Zaktualizowany kod dla Xcode, beta 7.
Aby to osiągnąć, nie potrzebujesz dopełnienia, ScrollViews ani List. Chociaż to rozwiązanie też się z nimi sprawdzi. Podaję tutaj dwa przykłady.
Pierwsza przesuwa cały tekst TextField w górę, jeśli pojawi się klawiatura dla któregokolwiek z nich. Ale tylko w razie potrzeby. Jeśli klawiatura nie zakrywa pól tekstowych, nie będą się one poruszać.
W drugim przykładzie widok przesuwa się tylko na tyle, aby uniknąć ukrycia aktywnego pola tekstowego.
Oba przykłady używają tego samego wspólnego kodu, który znajduje się na końcu: GeometryGetter i KeyboardGuardian
Pierwszy przykład (pokaż wszystkie pola tekstowe)
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))
}
}
Drugi przykład (pokaż tylko aktywne pole)
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
Jest to widok, który pochłania rozmiar i położenie widoku macierzystego. Aby to osiągnąć, jest wywoływana wewnątrz modyfikatora .background. To bardzo potężny modyfikator, a nie tylko sposób na dekorację tła widoku. Podczas przekazywania widoku do .background (MyView ()), MyView pobiera zmodyfikowany widok jako element nadrzędny. Użycie GeometryReader umożliwia widokowi poznanie geometrii elementu nadrzędnego.
Na przykład: Text("hello").background(GeometryGetter(rect: $bounds))
wypełni zmienne granice, rozmiarem i położeniem widoku Tekst i używając globalnego obszaru współrzędnych.
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)
}
}
}
}
Aktualizacja Dodałem DispatchQueue.main.async, aby uniknąć możliwości modyfikowania stanu widoku podczas renderowania. ***
KeyboardGuardian
Zadaniem KeyboardGuardian jest śledzenie zdarzeń pokazywania / ukrywania klawiatury i obliczanie, o ile miejsce należy przesunąć widok.
Aktualizacja: Zmodyfikowałem KeyboardGuardian, aby odświeżał slajd, gdy użytkownik przechodzi z jednego pola do drugiego
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)
}
}
}
}