Вам не нужны отступы, ScrollViews или списки для достижения этой цели.Хотя это решение будет хорошо с ними играть.Я включаю два примера здесь.
Первый перемещается all textField вверх, если клавиатура появляется для любого из них.Но только при необходимости.Если клавиатура не скрывает текстовые поля, они не будут перемещаться.
Во втором примере представление только перемещается достаточно просто, чтобы избежать скрытия активного текстового поля.
Оба примера используют один и тот жеобщий код, найденный в конце: GeometryGetter и KeyboardGuardian
Первый пример (показать все текстовые поля)
struct ContentView: View {
@ObjectBinding 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($name[0], placeholder: Text("enter text #1"))
.textFieldStyle(.roundedBorder)
TextField($name[1], placeholder: Text("enter text #2"))
.textFieldStyle(.roundedBorder)
TextField($name[2], placeholder: Text("enter text #3"))
.textFieldStyle(.roundedBorder)
.background(GeometryGetter(rect: $kGuardian.rects[0]))
}.offset(y: kGuardian.slide).animation(.basic(duration: 1.0))
}
}
Второй пример (показать только активное поле)
struct ContentView: View {
@ObjectBinding 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($name[0], placeholder: Text("enter text #1"), onEditingChanged: { if $0 { self.kGuardian.showField = 0 } })
.textFieldStyle(.roundedBorder)
.background(GeometryGetter(rect: $kGuardian.rects[0]))
TextField($name[1], placeholder: Text("enter text #2"), onEditingChanged: { if $0 { self.kGuardian.showField = 1 } })
.textFieldStyle(.roundedBorder)
.background(GeometryGetter(rect: $kGuardian.rects[1]))
TextField($name[2], placeholder: Text("enter text #3"), onEditingChanged: { if $0 { self.kGuardian.showField = 2 } })
.textFieldStyle(.roundedBorder)
.background(GeometryGetter(rect: $kGuardian.rects[2]))
}.offset(y: kGuardian.slide).animation(.basic(duration: 1.5))
}
}
GeometryGetter
Thisэто представление, которое поглощает размер и положение родительского представления.Для этого он вызывается внутри модификатора .background.Это очень мощный модификатор, а не просто способ украсить фон представления.При передаче представления в .background (MyView ()) MyView получает измененное представление в качестве родительского.Использование GeometryReader - это то, что позволяет представлению узнать геометрию родителя.
Например: Text("hello").background(GeomtryGetter(rect: $bounds))
заполнит границы переменных с размером и положением текстового представления и с использованием глобальной координатыпробел.
struct GeometryGetter: View {
@Binding var rect: CGRect
var body: some View {
GeometryReader { geometry in
Group { () -> ShapeView<Rectangle, Color> in
DispatchQueue.main.async {
self.rect = geometry.frame(in: .global)
}
return Rectangle().fill(Color.clear)
}
}
}
}
Обновление Я добавил DispatchQueue.main.async, чтобы избежать возможности изменения состояния представления во время его визуализации.***
KeyboardGuardian
Цель KeyboardGuardian - отслеживать события показа / скрытия клавиатуры и вычислять, сколько места необходимо сместить.
Обновление: Я изменил KeyboardGuardian для обновления слайда, когда пользователь перемещается из одного поля в другое
import SwiftUI
import Combine
final class KeyboardGuardian: BindableObject {
let didChange = PassthroughSubject<Void, Never>()
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
public var slide: Length = 0 {
didSet {
didChange.send()
}
}
public var showField: Int = 0 {
didSet {
updateSlide()
}
}
init(textFieldCount: Int) {
self.rects = Array<CGRect>(repeating: CGRect(), count: textFieldCount)
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)
}
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)
}
}
}
}