在SwiftUI中显示键盘时如何显示完整列表

时间:2019-06-22 14:32:59

标签: ios swift swiftui

当键盘出现时如何显示完整的列表?键盘隐藏在列表的下部。

我的列表行中有一个textField。当键盘出现时,无法向下滚动以查看完整列表。键盘在列表的前面,而不在列表的“下面”。这是我的编码:

struct ContentView: View {

    @State private var name = ""

    var body: some View {
        List {
            VStack {
                Text("Begin")
                    .frame(width: UIScreen.main.bounds.width)
                    .padding(.bottom, 400)
                    .background(Color.red)

                TextField($name, placeholder: Text("enter text"), onEditingChanged: { _ in
                    //
                }) {
                    //
                }

                Text("End")
                    .frame(width: UIScreen.main.bounds.width)
                    .padding(.top, 400)
                    .background(Color.green)
            }
            .listRowInsets(EdgeInsets())
        }
    }
}

有人可以帮我怎么做吗?

非常感谢您。

7 个答案:

答案 0 :(得分:10)

将Bogdan Farca出色的Combine方法更新为XCode 11.2:

"$fun"

答案 1 :(得分:3)

有一个answer here可以处理键盘操作, 您可以订阅这样的键盘事件:

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
    }
}

然后像这样使用它:

@State var keyboard = KeyboardResponder()
var body: some View {
        List {
            VStack {
             ...
             ...
             ...
            }.padding(.bottom, keyboard.currentHeight)
}

答案 2 :(得分:2)

让观察者设置EnvironmentValue。然后在您的视图中将其设为变量:

 @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)

  }

答案 3 :(得分:1)

这是BindableObject实现的更新版本(现称为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)
  }
}

在您的视图中使用它的方法如下:

@ObservedObject private var keyboardObserver = KeyboardObserver()

var body: some View {
  ...
  YourViewYouWantToRaise()
    .padding(.bottom, keyboardObserver.keyboardHeight)
    .animation(.easeInOut(duration: 0.3))
  ...
}

答案 4 :(得分:1)

使用SwiftUIX的单行解决方案

如果您安装SwiftUIX,则您所需要做的就是在包含列表的视图上调用.padding(.keyboard)。到目前为止,这是我所见过的最好,最简单的解决方案!

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!
    }
}

答案 5 :(得分:0)

使用KeyboardResponder的{​​{1}}对象的另一种实现,如here所示。

Compose

一种更好的方法是将以上内容打包为final class KeyboardResponder: BindableObject { 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() } } (从here改编而来):

ViewModifier

然后可以这样使用:

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)
    }
}

答案 6 :(得分:0)

这些示例有些旧,我修改了一些代码以使用最近添加到SwiftUI的新功能,可以在本文中找到此示例中使用的代码的详细说明:Article Describing ObservableObject

键盘观察器类:

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
    }
}

用法:

@ObservedObject private var keyboard = KeyboardResponder()

VStack {
 //Views here
}
//Makes it go up, since negative offset
.offset(y: -self.keyboard.currentHeight)