【问题标题】:How to get the keyboard height on multiple screens with SwiftUI and move the button如何使用 SwiftUI 在多个屏幕上获取键盘高度并移动按钮
【发布时间】: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 不能原生处理这个问题。还是我错过了什么?

标签: ios swift iphone swiftui xcode11


【解决方案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中按钮的移动。
  • 您忘记包含文本字段。我更新了我的答案以包含一个可行的解决方案。
  • 一旦有人开始在键盘上打字,这就会停止工作
猜你喜欢
  • 1970-01-01
  • 2017-11-01
  • 2018-06-15
  • 1970-01-01
  • 1970-01-01
  • 2012-07-02
  • 2011-05-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多