Code aktualisiert für den Xcode, Beta 7.
Sie benötigen keine Auffüllung, ScrollViews oder Listen, um dies zu erreichen. Obwohl diese Lösung auch mit ihnen gut spielen wird. Ich füge hier zwei Beispiele hinzu.
Der erste verschiebt den gesamten TextField nach oben, wenn die Tastatur für einen von ihnen angezeigt wird. Aber nur wenn nötig. Wenn die Tastatur die Textfelder nicht ausblendet, werden sie nicht verschoben.
Im zweiten Beispiel bewegt sich die Ansicht nur so weit, dass das aktive Textfeld nicht ausgeblendet wird.
Beide Beispiele verwenden denselben allgemeinen Code wie am Ende: GeometryGetter und KeyboardGuardian
Erstes Beispiel (alle Textfelder anzeigen)
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))
}
}
Zweites Beispiel (nur das aktive Feld anzeigen)
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
Dies ist eine Ansicht, die die Größe und Position der übergeordneten Ansicht absorbiert. Um dies zu erreichen, wird es im Modifikator .background aufgerufen. Dies ist ein sehr leistungsfähiger Modifikator, nicht nur eine Möglichkeit, den Hintergrund einer Ansicht zu dekorieren. Wenn Sie eine Ansicht an .background (MyView ()) übergeben, erhält MyView die geänderte Ansicht als übergeordnetes Element. Die Verwendung von GeometryReader ermöglicht es der Ansicht, die Geometrie des übergeordneten Elements zu kennen.
Beispiel: Text("hello").background(GeometryGetter(rect: $bounds))
Füllt variable Grenzen mit der Größe und Position der Textansicht und verwendet den globalen Koordinatenraum.
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)
}
}
}
}
Update Ich habe DispatchQueue.main.async hinzugefügt, um die Möglichkeit zu vermeiden, den Status der Ansicht während des Renderns zu ändern. ***
KeyboardGuardian
Der Zweck von KeyboardGuardian besteht darin, die Ereignisse zum Ein- und Ausblenden der Tastatur zu verfolgen und zu berechnen, wie viel Platz die Ansicht verschoben werden muss.
Update: Ich habe KeyboardGuardian geändert, um die Folie zu aktualisieren, wenn der Benutzer von einem Feld in ein anderes wechselt
import SwiftUI
import Combine
final class KeyboardGuardian: ObservableObject {
public var rects: Array<CGRect>
public var keyboardRect: CGRect = CGRect()
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)
}
}
}
}