简体   繁体   中英

SwiftUI List with TextField adjust when keyboard appears/disappears

I have written a List with SwiftUI. I also have a TextField object which is used as a search bar. My code looks like this:

import SwiftUI

struct MyListView: View {
    @ObservedObject var viewModel: MyViewModel

    @State private var query = ""

    var body: some View {

        NavigationView {
            List {
                // how to listen for changes here?
                // if I add onEditingChange here, Get the value only after the user finish search (by pressing enter on the keyboard)
                TextField(String.localizedString(forKey: "search_bar_hint"), text: self.$query) {
                    self.fetchListing()
                } 

                ForEach(viewModel.myArray, id: \.id) { arrayObject in
                    NavigationLink(destination: MyDetailView(MyDetailViewModel(arrayObj: arrayObject))) {
                         MyRow(arrayObj: arrayObject)
                    }
                }
            }
            .navigationBarTitle(navigationBarTitle())
        }
        .onAppear(perform: fetchListing)
    }

    private func fetchListing() {
        query.isEmpty ? viewModel.fetchRequest(for: nil) : viewModel.fetchRequest(for: query)
    }

    private func navigationBarTitle() -> String {
        return query.isEmpty ? String.localizedString(forKey: "my_title") : query
    }
}

The problem I have now is that the List remains behind the keyboard :(. How can I set the list padding bottom or edge insets (or whatever else works, I am totally open) so that the scrolling of the list ends above the keyboard? The list „size“ should also adjust automatically depending on if keyboard will be opened or closed.

Problem looks like this:

在此处输入图片说明

Please help me with any advice on this, I really have no idea how to do this :(. I am a SwiftUI beginner who is trying to learn it :).

You may try the following and add detailed animations by yourself.

@ObservedObject var keyboard = KeyboardResponder()
var body: some View {

    NavigationView {

        List {
            // how to listen for changes here?
            // if I add onEditingChange here, Get the value only after the user finish search (by pressing enter on the keyboard)
            TextField("search_bar_hint", text: self.$query) {
                self.fetchListing()
            }

            ForEach(self.viewModel, id: \.self) { arrayObject in
                Text(arrayObject)
            }

        }.padding(.bottom, self.keyboard.currentHeight).animation(.easeIn(duration: self.keyboard.keyboardDuration))
            .navigationBarTitle(self.navigationBarTitle())
    }
    .onAppear(perform: fetchListing)
}





class KeyboardResponder: ObservableObject {
@Published var currentHeight: CGFloat = 0
@Published var keyboardDuration: TimeInterval = 0
private var anyCancellable: Set<AnyCancellable> = Set<AnyCancellable>()

init() {
    let publisher1 = NotificationCenter.Publisher(center: .default, name: UIResponder.keyboardWillShowNotification).map{ notification -> Just<(CGFloat, TimeInterval)> in
    guard let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else {return Just((CGFloat(0.0), 0.0)) }
    guard let duration:TimeInterval = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else { return Just((CGFloat(0.0), 0.0)) }
    return Just((keyboardSize.height, duration))}

    let publisher2 = NotificationCenter.Publisher(center: .default, name: UIResponder.keyboardWillHideNotification) .map{ notification -> Just<(CGFloat, TimeInterval)> in
           guard let duration:TimeInterval = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else { return Just((CGFloat(0.0), 0.0)) }
               return Just((0.0, duration))}

    Publishers.Merge(publisher1, publisher2).switchToLatest().subscribe(on: RunLoop.main).sink(receiveValue: {
            if $0.1 > 1e-6 { self.currentHeight = $0.0 }
            self.keyboardDuration = $0.1
        }).store(in: &anyCancellable)
}
}

The resolution for the problem with the keyboard padding is like E.coms suggested. Also the class written here by kontiki can be used:

How to make the bottom button follow the keyboard display in SwiftUI

The problems I had was because of state changes in my view hierarchy due to multiple instances of reference types publishing similar state changes.

My view models are reference types, which publish changes to its models, which are value types. However, these view models also contain reference types which handle network requests. For each view I render (each row), I assign a new view model instance, which also creates a new network service instance. Continuing this pattern, each of these network services also create and assign new network managers.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM