如何使用 SwiftUI 在多个屏幕上获取键盘高度并移动按钮

Posted

技术标签:

【中文标题】如何使用 SwiftUI 在多个屏幕上获取键盘高度并移动按钮【英文标题】:How to get the keyboard height on multiple screens with SwiftUI and move the button 【发布时间】:2019-09-01 12:28:57 【问题描述】:

以下代码获取键盘显示时的键盘高度,并将按钮移动键盘高度。

在转换源(ContentView)和转换目标(SecibdContentView)以相同的方式执行此移动,但按钮不会在转换目标处移动。

如何使按钮在多个屏幕上移动相同?

import SwiftUI

struct ContentView: View 
    @ObservedObject private var keyboard = KeyboardResponder()

    var body: some View 
        NavigationView 
            VStack 
                Text("ContentView")

                Spacer()

                NavigationLink(destination: SecondContentView()) 
                    Text("Next")
                
                .offset(x: 0, y: -keyboard.currentHeight)
            
        
    


import SwiftUI

struct SecondContentView: View 
    @ObservedObject private var keyboard = KeyboardResponder()

    var body: some View 
        VStack 
            Text("SubContentView")

            Spacer()

            NavigationLink(destination: ThirdContentView()) 
                Text("Next")
            
            .offset(x: 0, y: -keyboard.currentHeight)
        
    


class KeyboardResponder: ObservableObject 
    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)
    

    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
    

【问题讨论】:

在模拟器上试过了,真是个解决办法。很遗憾看到 SwiftUI 不能原生处理这个问题。还是我错过了什么? 【参考方案1】:

使用 ViewModifier

你可以使用swiftui的ViewModifier就简单多了

import SwiftUI
import Combine

struct KeyboardAwareModifier: ViewModifier 
    @State private var keyboardHeight: CGFloat = 0

    private var keyboardHeightPublisher: AnyPublisher<CGFloat, Never> 
        Publishers.Merge(
            NotificationCenter.default
                .publisher(for: UIResponder.keyboardWillShowNotification)
                .compactMap  $0.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue 
                .map  $0.cgRectValue.height ,
            NotificationCenter.default
                .publisher(for: UIResponder.keyboardWillHideNotification)
                .map  _ in CGFloat(0) 
       ).eraseToAnyPublisher()
    

    func body(content: Content) -> some View 
        content
            .padding(.bottom, keyboardHeight)
            .onReceive(keyboardHeightPublisher)  self.keyboardHeight = $0 
    


extension View 
    func KeyboardAwarePadding() -> some View 
        ModifiedContent(content: self, modifier: KeyboardAwareModifier())
    

在你看来

struct SomeView: View 
    @State private var someText: String = ""

    var body: some View 
        VStack 
            Spacer()
            TextField("some text", text: $someText)
        .KeyboardAwarePadding()
    

KeyboardAwarePadding() 会自动在你的视图中添加一个 padding,这样更优雅。

【讨论】:

确实非常优雅的解决方案。处理 safeAreaInsets.bottom ``` func body(content: Content) -> some View content .padding(.bottom, keyboardHeight) .edgesIgnoringSafeArea(keyboardHeight==0 ? [] :.bottom) .onReceive(keyboardHeightPublisher) 的微小改动) self.keyboardHeight = $0 ``` 不错的解决方案,但是如何使它也适用于放置在滚动视图中的 VStack? 由于某种原因,我的键盘高度在69.0(隐藏时)和336.0(显示时)之间切换,从来没有 0.0 这很奇怪。有什么想法吗? @MatrosovAlexander,还添加一个 .edgesIgnoringSafeArea(.bottom)【参考方案2】:

SwiftUI + 组合

@Published var keyboardHeight: CGFloat = 0 // if one is in ViewModel: ObservableObject

private var cancellableSet: Set<AnyCancellable> = []
    
init() 
        
     NotificationCenter.default.publisher(for: UIWindow.keyboardWillShowNotification)
       .map 
             guard
                 let info = $0.userInfo,
                 let keyboardFrame = info[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect
                 else  return 0 

             return keyboardFrame.height
         
         .assign(to: \.keyboardHeight, on: self)
         .store(in: &cancellableSet)
        
     NotificationCenter.default.publisher(for: UIWindow.keyboardDidHideNotification)
         .map  _ in 0 
         .assign(to: \.keyboardHeight, on: self)
         .store(in: &cancellableSet)
    
    

【讨论】:

我猜这应该是在一个类而不是结构中吧?【参考方案3】:

您的代码缺少一些内容。没有 NavigationView,也没有让键盘出现的 TextField。考虑更新您的代码。

无论如何,通过将keyboardFrameBeginUserInfoKey 替换为keyboardFrameEndUserInfoKey 即可轻松解决问题。

更新:

您还需要使用相同的 KeyboardResponder,否则您将创建多个实例。或者,您可以将其放入您的环境中。

而且你忘记在代码中包含 TextFields,所以键盘会显示出来进行测试。

以下代码有效:

import SwiftUI

struct ContentView: View 
    @ObservedObject private var keyboard = KeyboardResponder()

    var body: some View 
        NavigationView 
            VStack 
                Text("ContentView")
                TextField("enter text", text: .constant(""))
                Spacer()

                NavigationLink(destination: SecondContentView(keyboard: keyboard)) 
                    Text("Next")
                
                .offset(x: 0, y: -keyboard.currentHeight)
            
        
    


import SwiftUI

struct SecondContentView: View 
    @ObservedObject var keyboard: KeyboardResponder

    var body: some View 
        VStack 
            Text("SubContentView")
            TextField("enter text", text: .constant(""))
            Spacer()

            NavigationLink(destination: Text("ThirdContentView()")) 
                Text("Next")
            
            .offset(x: 0, y: -keyboard.currentHeight)
        
    


class KeyboardResponder: ObservableObject 
    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)
    

    deinit 
        _center.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
    

【讨论】:

对不起。添加了导航视图。我将keyboardFrameBeginUserInfoKey更改为keyboardFrameEndUserInfoKey,但无法确认SubContentView中按钮的移动。 您忘记包含文本字段。我更新了我的答案以包含一个可行的解决方案。 一旦有人开始在键盘上打字,这就会停止工作

以上是关于如何使用 SwiftUI 在多个屏幕上获取键盘高度并移动按钮的主要内容,如果未能解决你的问题,请参考以下文章

如何在 React-Native 中获得键盘的高度?

如何在 macOS 上检测 SwiftUI 中的键盘事件?

如何在 macOS 上检测 SwiftUI 中的键盘事件?

如何计算 SwiftUI 中的标签栏高度?

SwiftUI DatePicker:有没有办法让他们不能使用键盘来编辑字段?

SwiftUI - 如何防止工作表中的键盘向上推我的主 UI