How to show complete List when keyboard is showing up in SwiftUI

前端 未结 7 568
广开言路
广开言路 2020-12-04 22:30

How is it possible to show the complete List when the keyboard is showing up? The keyboard is hiding the lower part of the list.

I have a textField in my list row. W

相关标签:
7条回答
  • 2020-12-04 23:01

    Have an observer set an EnvironmentValue. Then make that a variable in your View:

     @Environment(\.keyboardHeight) var keyboardHeight: CGFloat
    
    import SwiftUI
    import UIKit
    
    extension EnvironmentValues {
    
      var keyboardHeight : CGFloat {
        get { EnvironmentObserver.shared.keyboardHeight }
      }
    
    }
    
    class EnvironmentObserver {
    
      static let shared = EnvironmentObserver()
    
      var keyboardHeight: CGFloat = 0 {
        didSet { print("Keyboard height \(keyboardHeight)") }
      }
    
      init() {
    
        // MARK: Keyboard Events
    
        NotificationCenter.default.addObserver(forName: UIResponder.keyboardDidHideNotification, object: nil, queue: OperationQueue.main) { [weak self ] (notification) in
          self?.keyboardHeight = 0
        }
    
        let handler: (Notification) -> Void = { [weak self] notification in
            guard let userInfo = notification.userInfo else { return }
            guard let frame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }
    
            // From Apple docs:
            // The rectangle contained in the UIKeyboardFrameBeginUserInfoKey and UIKeyboardFrameEndUserInfoKey properties of the userInfo dictionary should be used only for the size information it contains. Do not use the origin of the rectangle (which is always {0.0, 0.0}) in rectangle-intersection operations. Because the keyboard is animated into position, the actual bounding rectangle of the keyboard changes over time.
    
            self?.keyboardHeight = frame.size.height
        }
    
        NotificationCenter.default.addObserver(forName: UIResponder.keyboardDidShowNotification, object: nil, queue: OperationQueue.main, using: handler)
    
        NotificationCenter.default.addObserver(forName: UIResponder.keyboardDidChangeFrameNotification, object: nil, queue: OperationQueue.main, using: handler)
    
      }
    
    0 讨论(0)
  • 2020-12-04 23:04

    These examples are a little old, I revamped some code to use the new features recently added to SwiftUI, detailed explanation of the code used in this sample can be found in this article: Article Describing ObservableObject

    Keyboard observer class:

    import SwiftUI
    import Combine
    
    final class KeyboardResponder: ObservableObject {
        let objectWillChange = ObservableObjectPublisher()
        private var _center: NotificationCenter
        @Published var currentHeight: CGFloat = 0
    
        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)
        }
    
        @objc func keyBoardWillShow(notification: Notification) {
            if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
                currentHeight = keyboardSize.height
            }
        }
    
        @objc func keyBoardWillHide(notification: Notification) {
            currentHeight = 0
        }
    }
    

    Usage:

    @ObservedObject private var keyboard = KeyboardResponder()
    
    VStack {
     //Views here
    }
    //Makes it go up, since negative offset
    .offset(y: -self.keyboard.currentHeight)
    
    0 讨论(0)
  • 2020-12-04 23:09

    Here's an updated version of the BindableObject implementation (now named ObservableObject).

    import SwiftUI
    import Combine
    
    class KeyboardObserver: ObservableObject {
    
      private var cancellable: AnyCancellable?
    
      @Published private(set) var keyboardHeight: CGFloat = 0
    
      let keyboardWillShow = NotificationCenter.default
        .publisher(for: UIResponder.keyboardWillShowNotification)
        .compactMap { ($0.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect)?.height }
    
      let keyboardWillHide = NotificationCenter.default
        .publisher(for: UIResponder.keyboardWillHideNotification)
        .map { _ -> CGFloat in 0 }
    
      init() {
        cancellable = Publishers.Merge(keyboardWillShow, keyboardWillHide)
          .subscribe(on: RunLoop.main)
          .assign(to: \.keyboardHeight, on: self)
      }
    }
    

    Here's how to use it in your views:

    @ObservedObject private var keyboardObserver = KeyboardObserver()
    
    var body: some View {
      ...
      YourViewYouWantToRaise()
        .padding(.bottom, keyboardObserver.keyboardHeight)
        .animation(.easeInOut(duration: 0.3))
      ...
    }
    
    0 讨论(0)
  • 2020-12-04 23:10

    there is an answer here to handle keyboard actions, you can subscribe for keyboard events like this:

    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) {
            if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
                currentHeight = keyboardSize.height
            }
        }
    
        @objc func keyBoardWillHide(notification: Notification) {
            currentHeight = 0
        }
    }
    

    and then just use it like this:

    @State var keyboard = KeyboardResponder()
    var body: some View {
            List {
                VStack {
                 ...
                 ...
                 ...
                }.padding(.bottom, keyboard.currentHeight)
    }
    
    0 讨论(0)
  • 2020-12-04 23:11

    One Line Solution with SwiftUIX

    If you install SwiftUIX, all you need to do is called, .padding(.keyboard) on the View that contains the list. This is by far the best and simplest solution I have seen!

    import SwiftUIX
    
    struct ExampleView: View {
        var body: some View {
            VStack {
               List {
                ForEach(contacts, id: \.self) { contact in
                    cellWithContact(contact)
                }
               }
            }.padding(.keyboard) // This is all that's needed, super cool!
        }
    }
    
    0 讨论(0)
  • 2020-12-04 23:15

    An alternative implementation of the KeyboardResponder object using Compose, as seen here.

    final class KeyboardResponder: ObservableObject {
    
        let willChange = PassthroughSubject<CGFloat, Never>()
    
        private(set) var currentHeight: Length = 0 {
            willSet {
                willChange.send(currentHeight)
            }
        }
    
        let keyboardWillOpen = NotificationCenter.default
            .publisher(for: UIResponder.keyboardWillShowNotification)
            .first() // keyboardWillShow notification may be posted repeatedly
            .map { $0.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as! CGRect }
            .map { $0.height }
    
        let keyboardWillHide =  NotificationCenter.default
            .publisher(for: UIResponder.keyboardWillHideNotification)
            .map { _ in CGFloat(0) }
    
        func listen() {
            _ = Publishers.Merge(keyboardWillOpen, keyboardWillHide)
                .subscribe(on: RunLoop.main)
                .assign(to: \.currentHeight, on: self)
        }
    
        init() {
            listen()
        }
    }
    

    An even nicer method is to pack the above as a ViewModifier (loosely adapted from here):

    struct AdaptsToSoftwareKeyboard: ViewModifier {
    
        @State var currentHeight: Length = 0
    
        func body(content: Content) -> some View {
            content
                .padding(.bottom, currentHeight)
                .edgesIgnoringSafeArea(currentHeight == 0 ? Edge.Set() : .bottom)
                .onAppear(perform: subscribeToKeyboardEvents)
        }
    
        private let keyboardWillOpen = NotificationCenter.default
            .publisher(for: UIResponder.keyboardWillShowNotification)
            .map { $0.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as! CGRect }
            .map { $0.height }
    
        private let keyboardWillHide =  NotificationCenter.default
            .publisher(for: UIResponder.keyboardWillHideNotification)
            .map { _ in Length.zero }
    
        private func subscribeToKeyboardEvents() {
            _ = Publishers.Merge(keyboardWillOpen, keyboardWillHide)
                .subscribe(on: RunLoop.main)
                .assign(to: \.currentHeight, on: self)
        }
    }
    

    And then it could be used like this:

    Group {
    
       ........
    
    }.modifier(AdaptsToSoftwareKeyboard())
    
    0 讨论(0)
提交回复
热议问题