我的主ContentView
中有七个TextField
。当用户打开键盘时,一些TextField
隐藏在键盘框架下。因此,当键盘出现时,我想将所有TextField
分别向上移动。
我使用下面的代码在屏幕上添加了TextField
。
struct ContentView : View {
@State var textfieldText: String = ""
var body: some View {
VStack {
TextField($textfieldText, placeholder: Text("TextField1"))
TextField($textfieldText, placeholder: Text("TextField2"))
TextField($textfieldText, placeholder: Text("TextField3"))
TextField($textfieldText, placeholder: Text("TextField4"))
TextField($textfieldText, placeholder: Text("TextField5"))
TextField($textfieldText, placeholder: Text("TextField6"))
TextField($textfieldText, placeholder: Text("TextField6"))
TextField($textfieldText, placeholder: Text("TextField7"))
}
}
}
输出:
发布于 2019-06-23 13:25:05
为Xcode更新的代码,beta 7。
您不需要填充、ScrollViews或列表来实现这一点。尽管这个解决方案也会和他们合作得很好。我在这里列举了两个例子。
第一个按钮将all textField向上移动(如果其中任何一个出现键盘的话)。但只有在需要的情况下。如果键盘不隐藏文本字段,它们将不会移动。
在第二个示例中,视图的移动仅仅是为了避免隐藏活动的文本字段。
这两个示例使用了在末尾找到的相同公共代码:GeometryGetter和KeyboardGuardian
第一个示例(显示所有文本字段)
struct ContentView: View {
@ObservedObject private var kGuardian = KeyboardGuardian(textFieldCount: 1)
@State private var name = Array<String>.init(repeating: "", count: 3)
var body: some View {
VStack {
Group {
Text("Some filler text").font(.largeTitle)
Text("Some filler text").font(.largeTitle)
}
TextField("enter text #1", text: $name[0])
.textFieldStyle(RoundedBorderTextFieldStyle())
TextField("enter text #2", text: $name[1])
.textFieldStyle(RoundedBorderTextFieldStyle())
TextField("enter text #3", text: $name[2])
.textFieldStyle(RoundedBorderTextFieldStyle())
.background(GeometryGetter(rect: $kGuardian.rects[0]))
}.offset(y: kGuardian.slide).animation(.easeInOut(duration: 1.0))
}
}
第二个示例(仅显示活动字段)
struct ContentView: View {
@ObservedObject private var kGuardian = KeyboardGuardian(textFieldCount: 3)
@State private var name = Array<String>.init(repeating: "", count: 3)
var body: some View {
VStack {
Group {
Text("Some filler text").font(.largeTitle)
Text("Some filler text").font(.largeTitle)
}
TextField("text #1", text: $name[0], onEditingChanged: { if $0 { self.kGuardian.showField = 0 } })
.textFieldStyle(RoundedBorderTextFieldStyle())
.background(GeometryGetter(rect: $kGuardian.rects[0]))
TextField("text #2", text: $name[1], onEditingChanged: { if $0 { self.kGuardian.showField = 1 } })
.textFieldStyle(RoundedBorderTextFieldStyle())
.background(GeometryGetter(rect: $kGuardian.rects[1]))
TextField("text #3", text: $name[2], onEditingChanged: { if $0 { self.kGuardian.showField = 2 } })
.textFieldStyle(RoundedBorderTextFieldStyle())
.background(GeometryGetter(rect: $kGuardian.rects[2]))
}.offset(y: kGuardian.slide).animation(.easeInOut(duration: 1.0))
}.onAppear { self.kGuardian.addObserver() }
.onDisappear { self.kGuardian.removeObserver() }
}
GeometryGetter
这是一个吸收了其父视图的大小和位置的视图。为了实现这一点,需要在.background修饰符内部调用它。这是一个非常强大的修饰符,不仅仅是装饰视图背景的一种方式。在将视图传递给.background( MyView ())时,MyView将获取修改后的视图作为父视图。通过使用GeometryReader,视图可以知道父对象的几何图形。
例如:Text("hello").background(GeometryGetter(rect: $bounds))
将使用文本视图的大小和位置填充可变边界,并使用全局坐标空间。
struct GeometryGetter: View {
@Binding var rect: CGRect
var body: some View {
GeometryReader { geometry in
Group { () -> AnyView in
DispatchQueue.main.async {
self.rect = geometry.frame(in: .global)
}
return AnyView(Color.clear)
}
}
}
}
更新我添加了DispatchQueue.main.async,以避免在呈现视图时修改视图状态的可能性。*
KeyboardGuardian
KeyboardGuardian的目的是跟踪键盘显示/隐藏事件,并计算需要移动视图的空间。
更新:我修改了KeyboardGuardian,以便在用户从一个字段切换到另一个字段时刷新幻灯片
import SwiftUI
import Combine
final class KeyboardGuardian: ObservableObject {
public var rects: Array<CGRect>
public var keyboardRect: CGRect = CGRect()
// keyboardWillShow notification may be posted repeatedly,
// this flag makes sure we only act once per keyboard appearance
public var keyboardIsHidden = true
@Published var slide: CGFloat = 0
var showField: Int = 0 {
didSet {
updateSlide()
}
}
init(textFieldCount: Int) {
self.rects = Array<CGRect>(repeating: CGRect(), count: textFieldCount)
}
func addObserver() {
NotificationCenter.default.addObserver(self, selector: #selector(keyBoardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyBoardDidHide(notification:)), name: UIResponder.keyboardDidHideNotification, object: nil)
}
func removeObserver() {
NotificationCenter.default.removeObserver(self)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
@objc func keyBoardWillShow(notification: Notification) {
if keyboardIsHidden {
keyboardIsHidden = false
if let rect = notification.userInfo?["UIKeyboardFrameEndUserInfoKey"] as? CGRect {
keyboardRect = rect
updateSlide()
}
}
}
@objc func keyBoardDidHide(notification: Notification) {
keyboardIsHidden = true
updateSlide()
}
func updateSlide() {
if keyboardIsHidden {
slide = 0
} else {
let tfRect = self.rects[self.showField]
let diff = keyboardRect.minY - tfRect.maxY
if diff > 0 {
slide += diff
} else {
slide += min(diff, 0)
}
}
}
}
发布于 2019-10-05 03:33:28
为了在@rraphael的解决方案的基础上构建,我将其转换为可用于今天的xcode11 swiftUI支持。
import SwiftUI
final class KeyboardResponder: ObservableObject {
private var notificationCenter: NotificationCenter
@Published private(set) var currentHeight: CGFloat = 0
init(center: NotificationCenter = .default) {
notificationCenter = center
notificationCenter.addObserver(self, selector: #selector(keyBoardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
notificationCenter.addObserver(self, selector: #selector(keyBoardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}
deinit {
notificationCenter.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
}
}
用法:
struct ContentView: View {
@ObservedObject private var keyboard = KeyboardResponder()
@State private var textFieldInput: String = ""
var body: some View {
VStack {
HStack {
TextField("uMessage", text: $textFieldInput)
}
}.padding()
.padding(.bottom, keyboard.currentHeight)
.edgesIgnoringSafeArea(.bottom)
.animation(.easeOut(duration: 0.16))
}
}
发布的currentHeight
将触发UI重新渲染,并在键盘显示时将TextField向上移动,在关闭时向下移动。然而,我没有使用ScrollView。
发布于 2020-02-12 06:32:23
我尝试了许多建议的解决方案,尽管它们在大多数情况下都有效,但我遇到了一些问题-主要是安全区域(我在TabView的选项卡中有一个表单)。
我最终组合了几种不同的解决方案,并使用GeometryReader来获得特定视图的安全区域底部嵌入,并在填充的计算中使用它:
import SwiftUI
import Combine
struct AdaptsToKeyboard: ViewModifier {
@State var currentHeight: CGFloat = 0
func body(content: Content) -> some View {
GeometryReader { geometry in
content
.padding(.bottom, self.currentHeight)
.onAppear(perform: {
NotificationCenter.Publisher(center: NotificationCenter.default, name: UIResponder.keyboardWillShowNotification)
.merge(with: NotificationCenter.Publisher(center: NotificationCenter.default, name: UIResponder.keyboardWillChangeFrameNotification))
.compactMap { notification in
withAnimation(.easeOut(duration: 0.16)) {
notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect
}
}
.map { rect in
rect.height - geometry.safeAreaInsets.bottom
}
.subscribe(Subscribers.Assign(object: self, keyPath: \.currentHeight))
NotificationCenter.Publisher(center: NotificationCenter.default, name: UIResponder.keyboardWillHideNotification)
.compactMap { notification in
CGFloat.zero
}
.subscribe(Subscribers.Assign(object: self, keyPath: \.currentHeight))
})
}
}
}
extension View {
func adaptsToKeyboard() -> some View {
return modifier(AdaptsToKeyboard())
}
}
用法:
struct MyView: View {
var body: some View {
Form {...}
.adaptsToKeyboard()
}
}
https://stackoverflow.com/questions/56491881
复制相似问题