在 SwiftUI 中开始文本字段编辑时检索键盘高度

Retrieving keyboard height when textfield editing starts in SwiftUI

所以我有一个 class,其中包含一个名为 keyboardHeight 的已发布变量,用于检索 keyboardHeight 的值:

import UIKit

class KeyboardHeightHelper: ObservableObject {
@Published var keyboardHeight: CGFloat = 0

init() {
    self.listenForKeyboardNotifications()
}

private func listenForKeyboardNotifications() {
    NotificationCenter.default.addObserver(forName: UIResponder.keyboardDidShowNotification,
                                           object: nil,
                                           queue: .main) { (notification) in
                                            guard let userInfo = notification.userInfo,
                                                let keyboardRect = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }
                                            
                                            self.keyboardHeight = keyboardRect.height
    }
    
    NotificationCenter.default.addObserver(forName: UIResponder.keyboardDidHideNotification,
                                           object: nil,
                                           queue: .main) { (notification) in
                                            self.keyboardHeight = 0
    }
}

}

然后,在 ContentView 中,我只有一个 TextField,当您 start/stop 编辑该字段时,它应该打印键盘高度:

import SwiftUI

struct ContentView: View {
    @State var textFieldText = ""
    @ObservedObject var keyboardHeightHelper = KeyboardHeightHelper()
    var body: some View {
    VStack {
        TextField("Text field",
                  text: $textFieldText, onEditingChanged: { _ in print("the keyboard height is \(self.keyboardHeightHelper.keyboardHeight)") })
        
    }

   }
}

我遇到的问题是:当我不编辑文本字段然后单击它时,它打印键盘高度为 0.0(我猜这是因为它在显示键盘之前获取了 keyboardHeight 值,所以在由于看不到键盘,高度为 0.0 的时间)。当我按下 return 并且键盘消失时,键盘的高度(对于 iPhone 8 模拟器)被打印为正确的值 260.0.我的问题是如何在开始编辑时访问键盘的值?

试试这个。您可以仅使用 SwiftUI 视图的修饰符将其集成:

extension UIResponder {
    static var currentFirstResponder: UIResponder? {
        _currentFirstResponder = nil
        UIApplication.shared.sendAction(#selector(UIResponder.findFirstResponder(_:)), to: nil, from: nil, for: nil)
        return _currentFirstResponder
    }

    private static weak var _currentFirstResponder: UIResponder?

    @objc private func findFirstResponder(_ sender: Any) {
        UIResponder._currentFirstResponder = self
    }
    
    var globalFrame: CGRect? {
        guard let view = self as? UIView else { return nil }
        return view.superview?.convert(view.frame, to: nil)
    }
}
extension Publishers {
    static var keyboardHeight: AnyPublisher<CGFloat, Never> {
        let willShow = NotificationCenter.default.publisher(for: UIApplication.keyboardWillShowNotification)
            .map { [=11=].keyboardHeight }
        
        let willHide = NotificationCenter.default.publisher(for: UIApplication.keyboardWillHideNotification)
            .map { _ in CGFloat(0) }
        
        return MergeMany(willShow, willHide)
            .eraseToAnyPublisher()
    }
}

extension Notification {
    var keyboardHeight: CGFloat {
        return (userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect)?.height ?? 0
    }
}
struct KeyboardAdaptive: ViewModifier {
    @State private var bottomPadding: CGFloat = 0

    func body(content: Content) -> some View {
        GeometryReader { geometry in
            content
                .padding(.bottom, self.bottomPadding)
                .onReceive(Publishers.keyboardHeight) { keyboardHeight in
                    let keyboardTop = geometry.frame(in: .global).height - keyboardHeight
                    let focusedTextInputBottom = UIResponder.currentFirstResponder?.globalFrame?.maxY ?? 0
                    self.bottomPadding = max(0, focusedTextInputBottom - keyboardTop - geometry.safeAreaInsets.bottom)
            }
            .animation(.easeOut(duration: 0.16))
        }
    }
}

extension View {
    func keyboardAdaptive() -> some View {
        ModifiedContent(content: self, modifier: KeyboardAdaptive())
    }
}

用法:

struct ContentView: View {
    @State private var text = ""
    
    var body: some View {
        VStack {
            Spacer()
            
            TextField("Enter something", text: $text)
                .textFieldStyle(RoundedBorderTextFieldStyle())
        }
        .padding()
        .keyboardAdaptive()//note this
    }
}

感谢多个在线资源。

.onEditingChanged 不是读取键盘高度的合适位置,因为您在点击 TextField 的那一刻就收到了这个回调,所以还没有显示键盘(因此,没有收到通知) .

相反,您可以明确地监听您的 keyboardHeight 属性 发布者,并在它发生变化时得到准确的通知(键盘通知是同步执行的,所以及时)

这是一个解决方案(用 Xcode 12 / iOS 14 测试)

VStack {
    TextField("Text field",
              text: $textFieldText, onEditingChanged: { _ in  })
        .onReceive(keyboardHeightHelper.$keyboardHeight) { value in
            print("the keyboard height is \(value)")
        }
}