Saya memiliki tujuh TextField
di dalam utama saya ContentView
. Saat pengguna membuka keyboard, beberapa di TextField
antaranya tersembunyi di bawah bingkai keyboard. Jadi saya ingin TextField
menaikkan semuanya masing-masing saat keyboard telah muncul.
Saya telah menggunakan kode di bawah ini untuk ditambahkan TextField
di layar.
struct ContentView : View {
@State var textfieldText: String = ""
var body: some View {
VStack {
TextField($textfieldText, placeholder: Text("TextField1"))
TextField($textfieldText, placeholder: Text("TextField2"))
TextField($textfieldText, placeholder: Text("TextField3"))
TextField($textfieldText, placeholder: Text("TextField4"))
TextField($textfieldText, placeholder: Text("TextField5"))
TextField($textfieldText, placeholder: Text("TextField6"))
TextField($textfieldText, placeholder: Text("TextField6"))
TextField($textfieldText, placeholder: Text("TextField7"))
}
}
}
Keluaran:
Jawaban:
Kode diperbarui untuk Xcode, beta 7.
Anda tidak perlu padding, ScrollView atau List untuk mencapai ini. Meskipun solusi ini akan menyenangkan mereka juga. Saya memasukkan dua contoh di sini.
Yang pertama memindahkan semua textField ke atas, jika keyboard muncul untuk salah satu dari mereka. Tetapi hanya jika diperlukan. Jika keyboard tidak menyembunyikan bidang teks, mereka tidak akan bergerak.
Pada contoh kedua, tampilan hanya bergerak cukup untuk menghindari persembunyian bidang teks aktif.
Kedua contoh menggunakan kode umum yang sama yang ditemukan di bagian akhir: GeometryGetter dan KeyboardGuardian
Contoh Pertama (tampilkan semua bidang teks)
struct ContentView: View { @ObservedObject private var kGuardian = KeyboardGuardian(textFieldCount: 1) @State private var name = Array<String>.init(repeating: "", count: 3) var body: some View { VStack { Group { Text("Some filler text").font(.largeTitle) Text("Some filler text").font(.largeTitle) } TextField("enter text #1", text: $name[0]) .textFieldStyle(RoundedBorderTextFieldStyle()) TextField("enter text #2", text: $name[1]) .textFieldStyle(RoundedBorderTextFieldStyle()) TextField("enter text #3", text: $name[2]) .textFieldStyle(RoundedBorderTextFieldStyle()) .background(GeometryGetter(rect: $kGuardian.rects[0])) }.offset(y: kGuardian.slide).animation(.easeInOut(duration: 1.0)) } }
Contoh Kedua (hanya menampilkan bidang aktif)
struct ContentView: View { @ObservedObject private var kGuardian = KeyboardGuardian(textFieldCount: 3) @State private var name = Array<String>.init(repeating: "", count: 3) var body: some View { VStack { Group { Text("Some filler text").font(.largeTitle) Text("Some filler text").font(.largeTitle) } TextField("text #1", text: $name[0], onEditingChanged: { if $0 { self.kGuardian.showField = 0 } }) .textFieldStyle(RoundedBorderTextFieldStyle()) .background(GeometryGetter(rect: $kGuardian.rects[0])) TextField("text #2", text: $name[1], onEditingChanged: { if $0 { self.kGuardian.showField = 1 } }) .textFieldStyle(RoundedBorderTextFieldStyle()) .background(GeometryGetter(rect: $kGuardian.rects[1])) TextField("text #3", text: $name[2], onEditingChanged: { if $0 { self.kGuardian.showField = 2 } }) .textFieldStyle(RoundedBorderTextFieldStyle()) .background(GeometryGetter(rect: $kGuardian.rects[2])) }.offset(y: kGuardian.slide).animation(.easeInOut(duration: 1.0)) }.onAppear { self.kGuardian.addObserver() } .onDisappear { self.kGuardian.removeObserver() } }
GeometryGetter
Ini adalah tampilan yang menyerap ukuran dan posisi tampilan induknya. Untuk mencapainya, ini dipanggil di dalam pengubah .background. Ini adalah pengubah yang sangat kuat, bukan hanya cara untuk menghias latar belakang tampilan. Saat meneruskan tampilan ke .background (MyView ()), MyView mendapatkan tampilan yang dimodifikasi sebagai induk. Penggunaan GeometryReader memungkinkan tampilan mengetahui geometri induknya.
Misalnya:
Text("hello").background(GeometryGetter(rect: $bounds))
akan mengisi batas variabel, dengan ukuran dan posisi tampilan Teks, dan menggunakan ruang koordinat global.struct GeometryGetter: View { @Binding var rect: CGRect var body: some View { GeometryReader { geometry in Group { () -> AnyView in DispatchQueue.main.async { self.rect = geometry.frame(in: .global) } return AnyView(Color.clear) } } } }
Pembaruan Saya menambahkan DispatchQueue.main.async, untuk menghindari kemungkinan mengubah status tampilan saat sedang dirender. ***
Penjaga Keyboard
Tujuan KeyboardGuardian, adalah untuk melacak acara keyboard / menyembunyikan acara dan menghitung berapa banyak ruang yang perlu digeser tampilan.
Pembaruan: Saya memodifikasi KeyboardGuardian untuk menyegarkan slide, ketika pengguna tab dari satu bidang ke bidang lainnya
import SwiftUI import Combine final class KeyboardGuardian: ObservableObject { public var rects: Array<CGRect> public var keyboardRect: CGRect = CGRect() // keyboardWillShow notification may be posted repeatedly, // this flag makes sure we only act once per keyboard appearance public var keyboardIsHidden = true @Published var slide: CGFloat = 0 var showField: Int = 0 { didSet { updateSlide() } } init(textFieldCount: Int) { self.rects = Array<CGRect>(repeating: CGRect(), count: textFieldCount) } func addObserver() { NotificationCenter.default.addObserver(self, selector: #selector(keyBoardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyBoardDidHide(notification:)), name: UIResponder.keyboardDidHideNotification, object: nil) } func removeObserver() { NotificationCenter.default.removeObserver(self) } deinit { NotificationCenter.default.removeObserver(self) } @objc func keyBoardWillShow(notification: Notification) { if keyboardIsHidden { keyboardIsHidden = false if let rect = notification.userInfo?["UIKeyboardFrameEndUserInfoKey"] as? CGRect { keyboardRect = rect updateSlide() } } } @objc func keyBoardDidHide(notification: Notification) { keyboardIsHidden = true updateSlide() } func updateSlide() { if keyboardIsHidden { slide = 0 } else { let tfRect = self.rects[self.showField] let diff = keyboardRect.minY - tfRect.maxY if diff > 0 { slide += diff } else { slide += min(diff, 0) } } } }
sumber
GeometryGetter
sebagai pengubah tampilan daripada latar belakang dengan membuatnya sesuai denganViewModifier
protokol?.modifier(GeometryGetter(rect: $kGuardian.rects[1]))
bukan.background(GeometryGetter(rect: $kGuardian.rects[1]))
. Tidak banyak perbedaan (hanya kurang 2 karakter).geometry.frame
keluar dari bantuanDispatchQueue.main.async
dengan SIGNAL ABORT, sekarang akan menguji solusi Anda. Pembaruan:if geometry.size.width > 0 && geometry.size.height > 0
sebelum menugaskanself.rect
membantu.Untuk membangun solusi @rraphael, saya mengubahnya menjadi dapat digunakan dengan dukungan swiftUI xcode11 hari ini.
import SwiftUI final class KeyboardResponder: ObservableObject { private var notificationCenter: NotificationCenter @Published private(set) var currentHeight: CGFloat = 0 init(center: NotificationCenter = .default) { notificationCenter = center notificationCenter.addObserver(self, selector: #selector(keyBoardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil) notificationCenter.addObserver(self, selector: #selector(keyBoardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil) } deinit { notificationCenter.removeObserver(self) } @objc func keyBoardWillShow(notification: Notification) { if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue { currentHeight = keyboardSize.height } } @objc func keyBoardWillHide(notification: Notification) { currentHeight = 0 } }
Pemakaian:
struct ContentView: View { @ObservedObject private var keyboard = KeyboardResponder() @State private var textFieldInput: String = "" var body: some View { VStack { HStack { TextField("uMessage", text: $textFieldInput) } }.padding() .padding(.bottom, keyboard.currentHeight) .edgesIgnoringSafeArea(.bottom) .animation(.easeOut(duration: 0.16)) } }
Terbit
currentHeight
akan memicu UI merender ulang dan memindahkan TextField Anda ke atas saat keyboard muncul, dan mundur saat ditutup. Namun saya tidak menggunakan ScrollView.sumber
.animation(.easeOut(duration: 0.16))
untuk mencoba mencocokkan kecepatan keyboard meluncur ke atas.keyboardFrameEndUserInfoKey
. Itu harus menahan bingkai terakhir untuk keyboard.Saya mencoba banyak solusi yang diusulkan, dan meskipun berfungsi dalam banyak kasus, saya memiliki beberapa masalah - terutama dengan area aman (Saya memiliki Formulir di dalam tab TabView).
Saya akhirnya menggabungkan beberapa solusi berbeda, dan menggunakan GeometryReader untuk mendapatkan inset bawah area aman tampilan tertentu dan menggunakannya dalam perhitungan padding:
import SwiftUI import Combine struct AdaptsToKeyboard: ViewModifier { @State var currentHeight: CGFloat = 0 func body(content: Content) -> some View { GeometryReader { geometry in content .padding(.bottom, self.currentHeight) .animation(.easeOut(duration: 0.16)) .onAppear(perform: { NotificationCenter.Publisher(center: NotificationCenter.default, name: UIResponder.keyboardWillShowNotification) .merge(with: NotificationCenter.Publisher(center: NotificationCenter.default, name: UIResponder.keyboardWillChangeFrameNotification)) .compactMap { notification in notification.userInfo?["UIKeyboardFrameEndUserInfoKey"] as? CGRect } .map { rect in rect.height - geometry.safeAreaInsets.bottom } .subscribe(Subscribers.Assign(object: self, keyPath: \.currentHeight)) NotificationCenter.Publisher(center: NotificationCenter.default, name: UIResponder.keyboardWillHideNotification) .compactMap { notification in CGFloat.zero } .subscribe(Subscribers.Assign(object: self, keyPath: \.currentHeight)) }) } } }
Pemakaian:
struct MyView: View { var body: some View { Form {...} .modifier(AdaptsToKeyboard()) } }
sumber
Thread 1: signal SIGABRT
onlinerect.height - geometry.safeAreaInsets.bottom
saat membuka tampilan dengan keyboard untuk kedua kalinya dan mengeklikTextField
. Tidak masalah jika saya mengklikTextField
pertama kali atau tidak. Aplikasi masih macet.Saya membuat Tampilan yang dapat membungkus tampilan lain untuk mengecilkannya saat keyboard muncul.
Sangat sederhana. Kami membuat penerbit untuk acara keyboard / menyembunyikan acara dan kemudian berlangganan menggunakan
onReceive
. Kami menggunakan hasil itu untuk membuat persegi panjang seukuran keyboard di belakang keyboard.struct KeyboardHost<Content: View>: View { let view: Content @State private var keyboardHeight: CGFloat = 0 private let showPublisher = NotificationCenter.Publisher.init( center: .default, name: UIResponder.keyboardWillShowNotification ).map { (notification) -> CGFloat in if let rect = notification.userInfo?["UIKeyboardFrameEndUserInfoKey"] as? CGRect { return rect.size.height } else { return 0 } } private let hidePublisher = NotificationCenter.Publisher.init( center: .default, name: UIResponder.keyboardWillHideNotification ).map {_ -> CGFloat in 0} // Like HStack or VStack, the only parameter is the view that this view should layout. // (It takes one view rather than the multiple views that Stacks can take) init(@ViewBuilder content: () -> Content) { view = content() } var body: some View { VStack { view Rectangle() .frame(height: keyboardHeight) .animation(.default) .foregroundColor(.clear) }.onReceive(showPublisher.merge(with: hidePublisher)) { (height) in self.keyboardHeight = height } } }
Anda kemudian dapat menggunakan tampilan seperti ini:
var body: some View { KeyboardHost { viewIncludingKeyboard() } }
Untuk memindahkan konten tampilan ke atas daripada mengecilkannya, padding atau offset dapat ditambahkan
view
daripada meletakkannya di VStack dengan persegi panjang.sumber
self.view
dan itu berfungsi dengan baik. Tidak ada masalah sama sekali dengan animasinyavar body: some View { VStack { view .padding(.bottom, keyboardHeight) .animation(.default) } .onReceive(showPublisher.merge(with: hidePublisher)) { (height) in self.keyboardHeight = height } }
Saya telah membuat pengubah tampilan yang sangat mudah digunakan.
Tambahkan file Swift dengan kode di bawah ini dan cukup tambahkan pengubah ini ke tampilan Anda:
import SwiftUI struct KeyboardResponsiveModifier: ViewModifier { @State private var offset: CGFloat = 0 func body(content: Content) -> some View { content .padding(.bottom, offset) .onAppear { NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: .main) { notif in let value = notif.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as! CGRect let height = value.height let bottomInset = UIApplication.shared.windows.first?.safeAreaInsets.bottom self.offset = height - (bottomInset ?? 0) } NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification, object: nil, queue: .main) { notif in self.offset = 0 } } } } extension View { func keyboardResponsive() -> ModifiedContent<Self, KeyboardResponsiveModifier> { return modifier(KeyboardResponsiveModifier()) } }
sumber
Atau Anda bisa menggunakan IQKeyBoardManagerSwift
dan secara opsional dapat menambahkan ini ke delegasi aplikasi Anda untuk menyembunyikan bilah alat dan mengaktifkan penyembunyian keyboard saat diklik pada tampilan apa pun selain keyboard.
IQKeyboardManager.shared.enableAutoToolbar = false IQKeyboardManager.shared.shouldShowToolbarPlaceholder = false IQKeyboardManager.shared.shouldResignOnTouchOutside = true IQKeyboardManager.shared.previousNextDisplayMode = .alwaysHide
sumber
IQKeyboardManager.shared.keyboardDistanceFromTextField
hingga 40 untuk mendapatkan celah yang nyaman.IQKeyboardManager.shared.enable = true
agar keyboard tidak menyembunyikan bidang teks saya. Bagaimanapun ini adalah solusi terbaik. Saya memiliki 4 bidang yang diatur secara vertikal dan solusi lainnya akan bekerja untuk bidang saya yang paling bawah, tetapi akan mendorong tampilan paling atas.Anda perlu menambahkan
ScrollView
dan mengatur bantalan bawah dari ukuran keyboard sehingga konten akan dapat bergulir saat keyboard muncul.Untuk mendapatkan ukuran keyboard, Anda harus menggunakan
NotificationCenter
untuk mendaftar ke acara keyboard. Anda dapat menggunakan kelas khusus untuk melakukannya:import SwiftUI import Combine final class KeyboardResponder: BindableObject { let didChange = PassthroughSubject<CGFloat, Never>() private var _center: NotificationCenter private(set) var currentHeight: CGFloat = 0 { didSet { didChange.send(currentHeight) } } init(center: NotificationCenter = .default) { _center = center _center.addObserver(self, selector: #selector(keyBoardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil) _center.addObserver(self, selector: #selector(keyBoardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil) } deinit { _center.removeObserver(self) } @objc func keyBoardWillShow(notification: Notification) { print("keyboard will show") if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue { currentHeight = keyboardSize.height } } @objc func keyBoardWillHide(notification: Notification) { print("keyboard will hide") currentHeight = 0 } }
The
BindableObject
kesesuaian akan memungkinkan Anda untuk menggunakan kelas ini sebagaiState
dan memicu pandangan pembaruan. Jika perlu, lihat tutorial untukBindableObject
: Tutorial SwiftUISaat Anda mendapatkannya, Anda perlu mengkonfigurasi a
ScrollView
untuk mengurangi ukurannya saat keyboard muncul. Untuk kenyamanan saya membungkus iniScrollView
menjadi beberapa jenis komponen:struct KeyboardScrollView<Content: View>: View { @State var keyboard = KeyboardResponder() private var content: Content init(@ViewBuilder content: () -> Content) { self.content = content() } var body: some View { ScrollView { VStack { content } } .padding(.bottom, keyboard.currentHeight) } }
Yang harus Anda lakukan sekarang adalah menyematkan konten Anda di dalam custom
ScrollView
.struct ContentView : View { @State var textfieldText: String = "" var body: some View { KeyboardScrollView { ForEach(0...10) { index in TextField(self.$textfieldText, placeholder: Text("TextField\(index)")) { // Hide keyboard when uses tap return button on keyboard. self.endEditing(true) } } } } private func endEditing(_ force: Bool) { UIApplication.shared.keyWindow?.endEditing(true) } }
Sunting: Perilaku gulir sangat aneh saat keyboard bersembunyi. Mungkin menggunakan animasi untuk memperbarui padding akan memperbaiki hal ini, atau Anda harus mempertimbangkan untuk menggunakan sesuatu selain
padding
untuk menyesuaikan ukuran tampilan gulir.sumber
BindableObject
Sayangnya, dengan deprecated, ini tidak berfungsi lagi.BindableObject
baru saja diganti namanya menjadiObservableObject
, dandidChange
menjadiobjectWillChange
. Objek memperbarui tampilan dengan baik (meskipun saya menguji menggunakan@ObservedObject
alih-alih@State
)Xcode 12 - Kode satu baris
Tambahkan pengubah ini ke
TextField
Apple menambahkan keyboard sebagai region untuk safe area, sehingga Anda dapat menggunakannya untuk berpindah -
View
pindah dengan keyboard seperti region lainnya.sumber
View
, termasukTextEditor
..ignoresSafeArea(.keyboard)
ke Tampilan Anda.Saya meninjau dan memperbaiki solusi yang ada menjadi paket SPM praktis yang menyediakan
.keyboardAware()
pengubah:KeyboardAwareSwiftUI
Contoh:
struct KeyboardAwareView: View { @State var text = "example" var body: some View { NavigationView { ScrollView { VStack(alignment: .leading) { ForEach(0 ..< 20) { i in Text("Text \(i):") TextField("Text", text: self.$text) .textFieldStyle(RoundedBorderTextFieldStyle()) .padding(.bottom, 10) } } .padding() } .keyboardAware() // <--- the view modifier .navigationBarTitle("Keyboard Example") } } }
Sumber:
import UIKit import SwiftUI public class KeyboardInfo: ObservableObject { public static var shared = KeyboardInfo() @Published public var height: CGFloat = 0 private init() { NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardChanged), name: UIApplication.keyboardWillShowNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardChanged), name: UIResponder.keyboardWillHideNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardChanged), name: UIResponder.keyboardWillChangeFrameNotification, object: nil) } @objc func keyboardChanged(notification: Notification) { if notification.name == UIApplication.keyboardWillHideNotification { self.height = 0 } else { self.height = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect)?.height ?? 0 } } } struct KeyboardAware: ViewModifier { @ObservedObject private var keyboard = KeyboardInfo.shared func body(content: Content) -> some View { content .padding(.bottom, self.keyboard.height) .edgesIgnoringSafeArea(self.keyboard.height > 0 ? .bottom : []) .animation(.easeOut) } } extension View { public func keyboardAware() -> some View { ModifiedContent(content: self, modifier: KeyboardAware()) } }
sumber
Saya menggunakan jawaban Benjamin Kindle sebagai titik awal, tetapi saya memiliki beberapa masalah yang ingin saya atasi.
keyboardWillChangeFrameNotification
ke daftar pemberitahuan diproses alamat ini.init
fungsi yang menerima a@ViewBuilder
sehingga Anda dapat menggunakanKeyboardHost
tampilan seperti View lainnya dan hanya meneruskan konten Anda di trailing closure, sebagai lawan meneruskan tampilan konten sebagai parameter keinit
.Rectangle
untuk menyesuaikan bantalan bawah.UIWindow
sebagaiUIWindow.keyboardFrameEndUserInfoKey
.Menarik semua itu saya miliki:
struct KeyboardHost<Content>: View where Content: View { var content: Content /// The current height of the keyboard rect. @State private var keyboardHeight = CGFloat(0) /// A publisher that combines all of the relevant keyboard changing notifications and maps them into a `CGFloat` representing the new height of the /// keyboard rect. private let keyboardChangePublisher = NotificationCenter.Publisher(center: .default, name: UIResponder.keyboardWillShowNotification) .merge(with: NotificationCenter.Publisher(center: .default, name: UIResponder.keyboardWillChangeFrameNotification)) .merge(with: NotificationCenter.Publisher(center: .default, name: UIResponder.keyboardWillHideNotification) // But we don't want to pass the keyboard rect from keyboardWillHide, so strip the userInfo out before // passing the notification on. .map { Notification(name: $0.name, object: $0.object, userInfo: nil) }) // Now map the merged notification stream into a height value. .map { ($0.userInfo?[UIWindow.keyboardFrameEndUserInfoKey] as? CGRect ?? .zero).size.height } // If you want to debug the notifications, swap this in for the final map call above. // .map { (note) -> CGFloat in // let height = (note.userInfo?[UIWindow.keyboardFrameEndUserInfoKey] as? CGRect ?? .zero).size.height // // print("Received \(note.name.rawValue) with height \(height)") // return height // } var body: some View { content .onReceive(keyboardChangePublisher) { self.keyboardHeight = $0 } .padding(.bottom, keyboardHeight) .animation(.default) } init(@ViewBuilder _ content: @escaping () -> Content) { self.content = content() } } struct KeyboardHost_Previews: PreviewProvider { static var previews: some View { KeyboardHost { TextField("TextField", text: .constant("Preview text field")) } } }
sumber
Keyboard
tinggiPridictive
di iOSkeyboard
.Settings
->General
->Keyboard
->Pridictive
. dalam hal ini tidak mengoreksi calclate dan menambahkan padding ke keyboardkeyboardHeight
. Di iPod Touch saya (dalam potret), keyboard dengan prediksi aktif adalah 254 poin. Tanpanya 216 poin. Saya bahkan dapat mematikan prediktif dengan keyboard di layar dan pembaruan padding dengan benar. Menambahkan keyboard dengan prediksi:Received UIKeyboardWillChangeFrameNotification with height 254.0
Received UIKeyboardWillShowNotification with height 254.0
Saat saya menonaktifkan teks prediksi:Received UIKeyboardWillChangeFrameNotification with height 216.0
Ini diadaptasi dari apa yang dibangun @kontiki. Saya menjalankannya di aplikasi di bawah beta 8 / GM seed, di mana bidang yang perlu digulir adalah bagian dari formulir di dalam NavigationView. Berikut KeyboardGuardian:
// // KeyboardGuardian.swift // // /programming/56491881/move-textfield-up-when-thekeyboard-has-appeared-by-using-swiftui-ios // import SwiftUI import Combine /// The purpose of KeyboardGuardian, is to keep track of keyboard show/hide events and /// calculate how much space the view needs to be shifted. final class KeyboardGuardian: ObservableObject { let objectWillChange = ObservableObjectPublisher() // PassthroughSubject<Void, Never>() public var rects: Array<CGRect> public var keyboardRect: CGRect = CGRect() // keyboardWillShow notification may be posted repeatedly, // this flag makes sure we only act once per keyboard appearance private var keyboardIsHidden = true var slide: CGFloat = 0 { didSet { objectWillChange.send() } } public var showField: Int = 0 { didSet { updateSlide() } } init(textFieldCount: Int) { self.rects = Array<CGRect>(repeating: CGRect(), count: textFieldCount) NotificationCenter.default.addObserver(self, selector: #selector(keyBoardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyBoardDidHide(notification:)), name: UIResponder.keyboardDidHideNotification, object: nil) } @objc func keyBoardWillShow(notification: Notification) { if keyboardIsHidden { keyboardIsHidden = false if let rect = notification.userInfo?["UIKeyboardFrameEndUserInfoKey"] as? CGRect { keyboardRect = rect updateSlide() } } } @objc func keyBoardDidHide(notification: Notification) { keyboardIsHidden = true updateSlide() } func updateSlide() { if keyboardIsHidden { slide = 0 } else { slide = -keyboardRect.size.height } } }
Kemudian, saya menggunakan enum untuk melacak slot dalam array rects dan jumlah totalnya:
enum KeyboardSlots: Int { case kLogPath case kLogThreshold case kDisplayClip case kPingInterval case count }
KeyboardSlots.count.rawValue
adalah kapasitas array yang diperlukan; yang lainnya sebagai rawValue memberikan indeks yang sesuai yang akan Anda gunakan untuk panggilan .background (GeometryGetter).Dengan pengaturan itu, pandangan masuk ke KeyboardGuardian dengan ini:
@ObservedObject private var kGuardian = KeyboardGuardian(textFieldCount: SettingsFormBody.KeyboardSlots.count.rawValue)
Gerakan sebenarnya seperti ini:
.offset(y: kGuardian.slide).animation(.easeInOut(duration: 1))
dilampirkan ke tampilan. Dalam kasus saya, itu dilampirkan ke seluruh NavigationView, sehingga perakitan lengkap meluncur saat keyboard muncul.
Saya belum memecahkan masalah mendapatkan toolbar Selesai atau tombol kembali pada keyboard desimal dengan SwiftUI, jadi saya menggunakan ini untuk menyembunyikannya dengan satu ketukan di tempat lain:
struct DismissingKeyboard: ViewModifier { func body(content: Content) -> some View { content .onTapGesture { let keyWindow = UIApplication.shared.connectedScenes .filter({$0.activationState == .foregroundActive}) .map({$0 as? UIWindowScene}) .compactMap({$0}) .first?.windows .filter({$0.isKeyWindow}).first keyWindow?.endEditing(true) } } }
Anda melampirkannya ke tampilan sebagai
.modifier(DismissingKeyboard())
Beberapa tampilan (mis., Alat pilih) tidak suka dilampirkan, jadi Anda mungkin perlu lebih terperinci dalam cara memasang pengubah daripada hanya menamparnya di tampilan terluar.
Terima kasih banyak kepada @kontiki atas kerja kerasnya. Anda masih membutuhkan GeometryGetter-nya di atas (tidak, saya tidak melakukan pekerjaan untuk mengubahnya menjadi menggunakan preferensi juga) seperti yang dia ilustrasikan dalam contoh-contohnya.
sumber
Beberapa solusi di atas memiliki beberapa masalah dan belum tentu merupakan pendekatan yang "paling bersih". Karena itu, saya telah memodifikasi beberapa hal untuk penerapan di bawah ini.
extension View { func onKeyboard(_ keyboardYOffset: Binding<CGFloat>) -> some View { return ModifiedContent(content: self, modifier: KeyboardModifier(keyboardYOffset)) } } struct KeyboardModifier: ViewModifier { @Binding var keyboardYOffset: CGFloat let keyboardWillAppearPublisher = NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification) let keyboardWillHidePublisher = NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification) init(_ offset: Binding<CGFloat>) { _keyboardYOffset = offset } func body(content: Content) -> some View { return content.offset(x: 0, y: -$keyboardYOffset.wrappedValue) .animation(.easeInOut(duration: 0.33)) .onReceive(keyboardWillAppearPublisher) { notification in let keyWindow = UIApplication.shared.connectedScenes .filter { $0.activationState == .foregroundActive } .map { $0 as? UIWindowScene } .compactMap { $0 } .first?.windows .filter { $0.isKeyWindow } .first let yOffset = keyWindow?.safeAreaInsets.bottom ?? 0 let keyboardFrame = (notification.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue ?? .zero self.$keyboardYOffset.wrappedValue = keyboardFrame.height - yOffset }.onReceive(keyboardWillHidePublisher) { _ in self.$keyboardYOffset.wrappedValue = 0 } } }
struct RegisterView: View { @State var name = "" @State var keyboardYOffset: CGFloat = 0 var body: some View { VStack { WelcomeMessageView() TextField("Type your name...", text: $name).bordered() }.onKeyboard($keyboardYOffset) .background(WelcomeBackgroundImage()) .padding() } }
Saya akan menyukai pendekatan yang lebih bersih dan untuk memindahkan tanggung jawab ke tampilan yang dibangun (bukan pengubah) dalam cara mengimbangi konten, tetapi tampaknya saya tidak bisa membuat penerbit memicu dengan benar saat memindahkan kode offset ke tampilan. ...
Perhatikan juga bahwa Publisher harus digunakan dalam contoh ini karena
final class
saat ini menyebabkan error pengecualian yang tidak diketahui (meskipun memenuhi persyaratan antarmuka) dan ScrollView secara keseluruhan adalah pendekatan terbaik saat menerapkan kode offset.sumber
Sejujurnya, banyak dari jawaban ini sepertinya sangat membengkak. Jika Anda menggunakan SwiftUI, Anda juga dapat menggunakan Gabungkan.
Buat
KeyboardResponder
seperti gambar di bawah ini, kemudian Anda dapat menggunakan seperti yang ditunjukkan sebelumnya.Diperbarui untuk iOS 14.
import Combine import UIKit final class KeyboardResponder: ObservableObject { @Published var keyboardHeight: CGFloat = 0 init() { NotificationCenter.default.publisher(for: UIResponder.keyboardWillChangeFrameNotification) .compactMap { notification in (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.height } .receive(on: DispatchQueue.main) .assign(to: \.keyboardHeight) } } struct ExampleView: View { @ObservedObject private var keyboardResponder = KeyboardResponder() @State private var text: String = "" var body: some View { VStack { Text(text) Spacer() TextField("Example", text: $text) } .padding(.bottom, keyboardResponder.keyboardHeight) } }
sumber
Saya tidak yakin apakah API transisi / animasi untuk SwiftUI sudah selesai, tetapi Anda dapat menggunakannya
CGAffineTransform
dengan.transformEffect
Buat objek keyboard yang dapat diamati dengan properti yang diterbitkan seperti ini:
final class KeyboardResponder: ObservableObject { private var notificationCenter: NotificationCenter @Published var readyToAppear = false init(center: NotificationCenter = .default) { notificationCenter = center notificationCenter.addObserver(self, selector: #selector(keyBoardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil) notificationCenter.addObserver(self, selector: #selector(keyBoardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil) } deinit { notificationCenter.removeObserver(self) } @objc func keyBoardWillShow(notification: Notification) { readyToAppear = true } @objc func keyBoardWillHide(notification: Notification) { readyToAppear = false } }
lalu Anda dapat menggunakan properti itu untuk mengatur ulang tampilan Anda seperti ini:
struct ContentView : View { @State var textfieldText: String = "" @ObservedObject private var keyboard = KeyboardResponder() var body: some View { return self.buildContent() } func buildContent() -> some View { let mainStack = VStack { TextField("TextField1", text: self.$textfieldText) TextField("TextField2", text: self.$textfieldText) TextField("TextField3", text: self.$textfieldText) TextField("TextField4", text: self.$textfieldText) TextField("TextField5", text: self.$textfieldText) TextField("TextField6", text: self.$textfieldText) TextField("TextField7", text: self.$textfieldText) } return Group{ if self.keyboard.readyToAppear { mainStack.transformEffect(CGAffineTransform(translationX: 0, y: -200)) .animation(.spring()) } else { mainStack } } } }
atau lebih sederhana
VStack { TextField("TextField1", text: self.$textfieldText) TextField("TextField2", text: self.$textfieldText) TextField("TextField3", text: self.$textfieldText) TextField("TextField4", text: self.$textfieldText) TextField("TextField5", text: self.$textfieldText) TextField("TextField6", text: self.$textfieldText) TextField("TextField7", text: self.$textfieldText) }.transformEffect(keyboard.readyToAppear ? CGAffineTransform(translationX: 0, y: -50) : .identity) .animation(.spring())
sumber
Xcode 12 beta 4 menambahkan pengubah tampilan baru
ignoresSafeArea
yang sekarang dapat Anda gunakan untuk menghindari keyboard..ignoresSafeArea([], edges: [])
Ini menghindari keyboard dan semua tepi area aman. Anda dapat mengatur parameter pertama ke
.keyboard
jika Anda tidak ingin dihindari. Ada beberapa keanehan di dalamnya, setidaknya dalam pengaturan hierarki tampilan saya, tetapi tampaknya inilah cara Apple ingin kita menghindari keyboard.sumber
Jawaban disalin dari sini: TextField selalu di atas keyboard dengan SwiftUI
Saya telah mencoba pendekatan yang berbeda, dan tidak ada yang berhasil untuk saya. Yang di bawah ini adalah satu-satunya yang berfungsi untuk perangkat yang berbeda.
Tambahkan ekstensi ini di file:
import SwiftUI import Combine extension View { func keyboardSensible(_ offsetValue: Binding<CGFloat>) -> some View { return self .padding(.bottom, offsetValue.wrappedValue) .animation(.spring()) .onAppear { NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: .main) { notification in let keyWindow = UIApplication.shared.connectedScenes .filter({$0.activationState == .foregroundActive}) .map({$0 as? UIWindowScene}) .compactMap({$0}) .first?.windows .filter({$0.isKeyWindow}).first let bottom = keyWindow?.safeAreaInsets.bottom ?? 0 let value = notification.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as! CGRect let height = value.height offsetValue.wrappedValue = height - bottom } NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification, object: nil, queue: .main) { _ in offsetValue.wrappedValue = 0 } } } }
Dalam tampilan Anda, Anda memerlukan variabel untuk mengikat offsetValue:
struct IncomeView: View { @State private var offsetValue: CGFloat = 0.0 var body: some View { VStack { //... } .keyboardSensible($offsetValue) } }
sumber
NotificationCenter.default.addObserver
... Anda perlu menyimpannya dan menghapus pengamat pada waktu yang tepat ...Seperti yang telah ditunjukkan oleh Mark Krenek dan Heiko, Apple tampaknya menangani masalah ini pada akhirnya di Xcode 12 beta 4. Segalanya bergerak dengan cepat. Menurut catatan rilis untuk Xcode 12 beta 5 yang diterbitkan 18 Agustus 2020 "Formulir, Daftar, dan TextEditor tidak lagi menyembunyikan konten di balik keyboard. (66172025)". Saya baru saja mengunduhnya dan mencobanya dengan cepat di simulator beta 5 (iPhone SE2) dengan wadah Formulir di aplikasi yang saya mulai beberapa hari yang lalu.
Sekarang "hanya bekerja" untuk TextField . SwiftUI secara otomatis akan memberikan bantalan bawah yang sesuai ke Formulir enkapsulasi untuk memberi ruang bagi keyboard. Dan secara otomatis akan menggulir Form ke atas untuk menampilkan TextField tepat di atas keyboard. Container ScrollView sekarang berfungsi dengan baik saat keyboard muncul juga.
Namun, seperti yang Андрей Первушин tunjukkan dalam komentar, ada masalah dengan TextEditor . Beta 5 & 6 secara otomatis akan memberikan bantalan bawah yang sesuai ke Formulir enkapsulasi untuk memberi ruang bagi keyboard. Tetapi TIDAK akan secara otomatis menggulir Formulir ke atas. Keyboard akan menutupi TextEditor. Jadi tidak seperti TextField, pengguna harus menggulir Formulir untuk membuat TextEditor terlihat. Saya akan mengajukan laporan bug. Mungkin Beta 7 akan memperbaikinya. Sangat dekat…
https://developer.apple.com/documentation/ios-ipados-release-notes/ios-ipados-14-beta-release-notes/
sumber
Pemakaian:
import SwiftUI var body: some View { ScrollView { VStack { /* TextField() */ } }.keyboardSpace() }
Kode:
import SwiftUI import Combine let keyboardSpaceD = KeyboardSpace() extension View { func keyboardSpace() -> some View { modifier(KeyboardSpace.Space(data: keyboardSpaceD)) } } class KeyboardSpace: ObservableObject { var sub: AnyCancellable? @Published var currentHeight: CGFloat = 0 var heightIn: CGFloat = 0 { didSet { withAnimation { if UIWindow.keyWindow != nil { //fix notification when switching from another app with keyboard self.currentHeight = heightIn } } } } init() { subscribeToKeyboardEvents() } private let keyboardWillOpen = NotificationCenter.default .publisher(for: UIResponder.keyboardWillShowNotification) .map { $0.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as! CGRect } .map { $0.height - (UIWindow.keyWindow?.safeAreaInsets.bottom ?? 0) } private let keyboardWillHide = NotificationCenter.default .publisher(for: UIResponder.keyboardWillHideNotification) .map { _ in CGFloat.zero } private func subscribeToKeyboardEvents() { sub?.cancel() sub = Publishers.Merge(keyboardWillOpen, keyboardWillHide) .subscribe(on: RunLoop.main) .assign(to: \.self.heightIn, on: self) } deinit { sub?.cancel() } struct Space: ViewModifier { @ObservedObject var data: KeyboardSpace func body(content: Content) -> some View { VStack(spacing: 0) { content Rectangle() .foregroundColor(Color(.clear)) .frame(height: data.currentHeight) .frame(maxWidth: .greatestFiniteMagnitude) } } } } extension UIWindow { static var keyWindow: UIWindow? { let keyWindow = UIApplication.shared.connectedScenes .filter({$0.activationState == .foregroundActive}) .map({$0 as? UIWindowScene}) .compactMap({$0}) .first?.windows .filter({$0.isKeyWindow}).first return keyWindow } }
sumber
Penanganan
TabView
'sSaya suka jawaban Benjamin Kindle tetapi tidak mendukung TabView. Berikut adalah penyesuaian saya pada kodenya untuk menangani TabView:
UITabView
untuk menyimpan ukuran tabView saat bingkainya disetel. Kita dapat menyimpan ini dalam variabel statis karena biasanya hanya ada satu tabView dalam sebuah proyek (jika milik Anda memiliki lebih dari satu, Anda harus menyesuaikannya).extension UITabBar { static var size: CGSize = .zero open override var frame: CGRect { get { super.frame } set { UITabBar.size = newValue.size super.frame = newValue } } }
onReceive
di bagian bawahKeyboardHost
tampilan untuk memperhitungkan tinggi Tab Bar:.onReceive(showPublisher.merge(with: hidePublisher)) { (height) in self.keyboardHeight = max(height - UITabBar.size.height, 0) }
sumber
Saya mengambil pendekatan yang sama sekali berbeda, dengan memperluas
UIHostingController
dan menyesuaikannyaadditionalSafeAreaInsets
:class MyHostingController<Content: View>: UIHostingController<Content> { override init(rootView: Content) { super.init(rootView: rootView) } @objc required dynamic init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidShow(_:)), name: UIResponder.keyboardDidShowNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil) } @objc func keyboardDidShow(_ notification: Notification) { guard let info:[AnyHashable: Any] = notification.userInfo, let frame = info[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return } // set the additionalSafeAreaInsets let adjustHeight = frame.height - (self.view.safeAreaInsets.bottom - self.additionalSafeAreaInsets.bottom) self.additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: adjustHeight, right: 0) // now try to find a UIResponder inside a ScrollView, and scroll // the firstResponder into view DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) { if let firstResponder = UIResponder.findFirstResponder() as? UIView, let scrollView = firstResponder.parentScrollView() { // translate the firstResponder's frame into the scrollView's coordinate system, // with a little vertical padding let rect = firstResponder.convert(firstResponder.frame, to: scrollView) .insetBy(dx: 0, dy: -15) scrollView.scrollRectToVisible(rect, animated: true) } } } @objc func keyboardWillHide() { self.additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) } } /// IUResponder extension for finding the current first responder extension UIResponder { private struct StaticFirstResponder { static weak var firstResponder: UIResponder? } /// find the current first responder, or nil static func findFirstResponder() -> UIResponder? { StaticFirstResponder.firstResponder = nil UIApplication.shared.sendAction( #selector(UIResponder.trap), to: nil, from: nil, for: nil) return StaticFirstResponder.firstResponder } @objc private func trap() { StaticFirstResponder.firstResponder = self } } /// UIView extension for finding the receiver's parent UIScrollView extension UIView { func parentScrollView() -> UIScrollView? { if let scrollView = self.superview as? UIScrollView { return scrollView } return superview?.parentScrollView() } }
Kemudian ubah
SceneDelegate
untuk menggunakan,MyHostingController
bukanUIHostingController
.Setelah selesai, saya tidak perlu khawatir tentang keyboard di dalam kode SwiftUI saya.
(Catatan: Saya belum cukup menggunakan ini, untuk sepenuhnya memahami implikasi apa pun dari melakukan ini!)
sumber
Ini adalah cara saya menangani keyboard di SwiftUI. Hal yang perlu diingat adalah bahwa itu membuat perhitungan pada VStack yang dilampirkan.
Anda menggunakannya di Tampilan sebagai Pengubah. Cara ini:
struct LogInView: View { var body: some View { VStack { // Your View } .modifier(KeyboardModifier()) } }
Jadi untuk sampai ke pengubah ini, pertama, buat ekstensi UIResponder untuk mendapatkan posisi TextField yang dipilih di VStack:
import UIKit // MARK: Retrieve TextField first responder for keyboard extension UIResponder { private static weak var currentResponder: UIResponder? static var currentFirstResponder: UIResponder? { currentResponder = nil UIApplication.shared.sendAction(#selector(UIResponder.findFirstResponder), to: nil, from: nil, for: nil) return currentResponder } @objc private func findFirstResponder(_ sender: Any) { UIResponder.currentResponder = self } // Frame of the superview var globalFrame: CGRect? { guard let view = self as? UIView else { return nil } return view.superview?.convert(view.frame, to: nil) } }
Anda sekarang dapat membuat KeyboardModifier menggunakan Gabungkan untuk menghindari keyboard menyembunyikan BidangTeks:
import SwiftUI import Combine // MARK: Keyboard show/hide VStack offset modifier struct KeyboardModifier: ViewModifier { @State var offset: CGFloat = .zero @State var subscription = Set<AnyCancellable>() func body(content: Content) -> some View { GeometryReader { geometry in content .padding(.bottom, self.offset) .animation(.spring(response: 0.4, dampingFraction: 0.5, blendDuration: 1)) .onAppear { NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification) .handleEvents(receiveOutput: { _ in self.offset = 0 }) .sink { _ in } .store(in: &self.subscription) NotificationCenter.default.publisher(for: UIResponder.keyboardWillChangeFrameNotification) .map(\.userInfo) .compactMap { ($0?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect)?.size.height } .sink(receiveValue: { keyboardHeight in let keyboardTop = geometry.frame(in: .global).height - keyboardHeight let textFieldBottom = UIResponder.currentFirstResponder?.globalFrame?.maxY ?? 0 self.offset = max(0, textFieldBottom - keyboardTop * 2 - geometry.safeAreaInsets.bottom) }) .store(in: &self.subscription) } .onDisappear { // Dismiss keyboard UIApplication.shared.windows .first { $0.isKeyWindow }? .endEditing(true) self.subscription.removeAll() } } } }
sumber
Adapun iOS 14 (beta 4) berfungsi cukup sederhana:
var body: some View { VStack { TextField(...) } .padding(.bottom, 0) }
Dan ukuran tampilan menyesuaikan dengan bagian atas keyboard. Tentunya ada lebih banyak perbaikan yang mungkin dilakukan dengan frame (.maxHeight: ...) dll. Anda akan mengetahuinya.
Sayangnya floating keyboard di iPad masih menimbulkan masalah saat dipindahkan. Tetapi solusi yang disebutkan di atas juga akan, dan ini masih beta, saya harap mereka akan mengetahuinya.
Terima kasih Apple, akhirnya!
sumber
Pandangan ku:
struct AddContactView: View { @Environment(\.presentationMode) var presentationMode : Binding<PresentationMode> @ObservedObject var addContactVM = AddContactVM() @State private var offsetValue: CGFloat = 0.0 @State var firstName : String @State var lastName : String @State var sipAddress : String @State var phoneNumber : String @State var emailID : String var body: some View { VStack{ Header(title: StringConstants.ADD_CONTACT) { self.presentationMode.wrappedValue.dismiss() } ScrollView(Axis.Set.vertical, showsIndicators: false){ Image("contactAvatar") .padding(.top, 80) .padding(.bottom, 100) //.padding(.vertical, 100) //.frame(width: 60,height : 60).aspectRatio(1, contentMode: .fit) VStack(alignment: .center, spacing: 0) { TextFieldBorder(placeHolder: StringConstants.FIRST_NAME, currentText: firstName, imageName: nil) TextFieldBorder(placeHolder: StringConstants.LAST_NAME, currentText: lastName, imageName: nil) TextFieldBorder(placeHolder: StringConstants.SIP_ADDRESS, currentText: sipAddress, imageName: "sipPhone") TextFieldBorder(placeHolder: StringConstants.PHONE_NUMBER, currentText: phoneNumber, imageName: "phoneIcon") TextFieldBorder(placeHolder: StringConstants.EMAILID, currentText: emailID, imageName: "email") } Spacer() } .padding(.horizontal, 20) } .padding(.bottom, self.addContactVM.bottomPadding) .onAppear { NotificationCenter.default.addObserver(self.addContactVM, selector: #selector(self.addContactVM.keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil) NotificationCenter.default.addObserver(self.addContactVM, selector: #selector(self.addContactVM.keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil) } } }
VM saya:
class AddContactVM : ObservableObject{ @Published var contact : Contact = Contact(id: "", firstName: "", lastName: "", phoneNumbers: [], isAvatarAvailable: false, avatar: nil, emailID: "") @Published var bottomPadding : CGFloat = 0.0 @objc func keyboardWillShow(_ notification : Notification){ if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue { let keyboardRectangle = keyboardFrame.cgRectValue let keyboardHeight = keyboardRectangle.height self.bottomPadding = keyboardHeight } } @objc func keyboardWillHide(_ notification : Notification){ self.bottomPadding = 0.0 } }
Pada dasarnya, Mengelola bantalan bawah berdasarkan ketinggian keyboard.
sumber
Jawaban paling elegan yang pernah saya lakukan ini mirip dengan solusi rraphael. Buat kelas untuk mendengarkan acara keyboard. Alih-alih menggunakan ukuran keyboard untuk memodifikasi padding, kembalikan nilai negatif dari ukuran keyboard, dan gunakan pengubah .offset (y :) untuk menyesuaikan offset penampung tampilan paling luar. Ini beranimasi dengan cukup baik, dan berfungsi dengan tampilan apa pun.
sumber
.offset(y: withAnimation { -keyboard.currentHeight })
, tapi kontennya melompat, bukan animasi.