如何在不同子视图中使用 TextFields 更改 SwiftUI 应用程序的 FocusState,而不会导致跳动效果的视图刷新?
How can one change the FocusState of a SwiftUI app with TextFields in different child views without having View refresh which causes a bounce effect?
我的问题:我希望用户能够从 Textfield 转到 TextField,而不会弹跳视图,如下面的 gif 所示。
我的用例: 我在多个子视图中有多个 TextFields 和 TextEditor。这些 TextFields 是动态生成的,所以我希望 FocusState 是一个单独的问题。
我在下面制作了一个示例 gif 和代码示例。
请查看,如有任何建议,我们将不胜感激。
按照评论中的建议,我做了一些对弹跳没有影响的更改:
- Using Identfiable does not change the bounce
- A single observed object or multiple and a view model does not change the bounce
我认为这是来自状态变化刷新。如果不是刷新导致弹跳(正如用户在评论中建议的那样),那是什么?使用 FocusState 时有没有办法阻止这种反弹?
重现: 创建一个新的 iOS 应用程序 xcode 项目并用下面的代码主体替换内容视图。当用户从一个文本字段转到下一个文本字段导致整个屏幕弹跳时,它似乎刷新了视图。
代码示例
import SwiftUI
struct MyObject: Identifiable, Equatable {
var id: String
public var value: String
init(name: String, value: String) {
self.id = name
self.value = value
}
}
struct ContentView: View {
@State var myObjects: [MyObject] = [
MyObject(name: "aa", value: "1"),
MyObject(name: "bb", value: "2"),
MyObject(name: "cc", value: "3"),
MyObject(name: "dd", value: "4")
]
@State var focus: MyObject?
var body: some View {
VStack {
Text("Header")
ForEach(self.myObjects) { obj in
Divider()
FocusField(displayObject: obj, focus: $focus, nextFocus: {
guard let index = self.myObjects.firstIndex(of: [=11=]) else {
return
}
self.focus = myObjects.indices.contains(index + 1) ? myObjects[index + 1] : nil
})
}
Divider()
Text("Footer")
}
}
}
struct FocusField: View {
@State var displayObject: MyObject
@FocusState var isFocused: Bool
@Binding var focus: MyObject?
var nextFocus: (MyObject) -> Void
var body: some View {
TextField("Test", text: $displayObject.value)
.onChange(of: focus, perform: { newValue in
self.isFocused = newValue == displayObject
})
.focused(self.$isFocused)
.submitLabel(.next)
.onSubmit {
self.nextFocus(displayObject)
}
}
}
经历了很多次之后,我突然意识到,在使用 FocusState
时,您确实应该处于 ScrollView
、Form
或其他类型的贪婪视图中.即使是 GeometryReader
也可以。任何这些都会消除反弹。
struct MyObject: Identifiable, Equatable {
public let id = UUID()
public var name: String
public var value: String
}
class MyObjViewModel: ObservableObject {
@Published var myObjects: [MyObject]
init(_ objects: [MyObject]) {
myObjects = objects
}
}
struct ContentView: View {
@StateObject var viewModel = MyObjViewModel([
MyObject(name: "aa", value: "1"),
MyObject(name: "bb", value: "2"),
MyObject(name: "cc", value: "3"),
MyObject(name: "dd", value: "4")
])
@State var focus: UUID?
var body: some View {
VStack {
Form {
Text("Header")
ForEach($viewModel.myObjects) { $obj in
FocusField(object: $obj, focus: $focus, nextFocus: {
guard let index = viewModel.myObjects.map( { [=10=].id }).firstIndex(of: obj.id) else {
return
}
focus = viewModel.myObjects.indices.contains(index + 1) ? viewModel.myObjects[index + 1].id : viewModel.myObjects[0].id
})
}
Text("Footer")
}
}
}
}
struct FocusField: View {
@Binding var object: MyObject
@Binding var focus: UUID?
var nextFocus: () -> Void
@FocusState var isFocused: UUID?
var body: some View {
TextField("Test", text: $object.value)
.onChange(of: focus, perform: { newValue in
self.isFocused = newValue
})
.focused(self.$isFocused, equals: object.id)
.onSubmit {
self.nextFocus()
}
}
}
编辑:
此外,按照您的方式在结构中设置 id
是一个非常糟糕的主意。 id 应该是唯一的。它在这里有效,但最佳做法是 UUID
.
第二次编辑:收紧代码。
我的问题:我希望用户能够从 Textfield 转到 TextField,而不会弹跳视图,如下面的 gif 所示。
我的用例: 我在多个子视图中有多个 TextFields 和 TextEditor。这些 TextFields 是动态生成的,所以我希望 FocusState 是一个单独的问题。
我在下面制作了一个示例 gif 和代码示例。
请查看,如有任何建议,我们将不胜感激。
按照评论中的建议,我做了一些对弹跳没有影响的更改:
- Using Identfiable does not change the bounce
- A single observed object or multiple and a view model does not change the bounce
我认为这是来自状态变化刷新。如果不是刷新导致弹跳(正如用户在评论中建议的那样),那是什么?使用 FocusState 时有没有办法阻止这种反弹?
重现: 创建一个新的 iOS 应用程序 xcode 项目并用下面的代码主体替换内容视图。当用户从一个文本字段转到下一个文本字段导致整个屏幕弹跳时,它似乎刷新了视图。
代码示例
import SwiftUI
struct MyObject: Identifiable, Equatable {
var id: String
public var value: String
init(name: String, value: String) {
self.id = name
self.value = value
}
}
struct ContentView: View {
@State var myObjects: [MyObject] = [
MyObject(name: "aa", value: "1"),
MyObject(name: "bb", value: "2"),
MyObject(name: "cc", value: "3"),
MyObject(name: "dd", value: "4")
]
@State var focus: MyObject?
var body: some View {
VStack {
Text("Header")
ForEach(self.myObjects) { obj in
Divider()
FocusField(displayObject: obj, focus: $focus, nextFocus: {
guard let index = self.myObjects.firstIndex(of: [=11=]) else {
return
}
self.focus = myObjects.indices.contains(index + 1) ? myObjects[index + 1] : nil
})
}
Divider()
Text("Footer")
}
}
}
struct FocusField: View {
@State var displayObject: MyObject
@FocusState var isFocused: Bool
@Binding var focus: MyObject?
var nextFocus: (MyObject) -> Void
var body: some View {
TextField("Test", text: $displayObject.value)
.onChange(of: focus, perform: { newValue in
self.isFocused = newValue == displayObject
})
.focused(self.$isFocused)
.submitLabel(.next)
.onSubmit {
self.nextFocus(displayObject)
}
}
}
经历了很多次之后,我突然意识到,在使用 FocusState
时,您确实应该处于 ScrollView
、Form
或其他类型的贪婪视图中.即使是 GeometryReader
也可以。任何这些都会消除反弹。
struct MyObject: Identifiable, Equatable {
public let id = UUID()
public var name: String
public var value: String
}
class MyObjViewModel: ObservableObject {
@Published var myObjects: [MyObject]
init(_ objects: [MyObject]) {
myObjects = objects
}
}
struct ContentView: View {
@StateObject var viewModel = MyObjViewModel([
MyObject(name: "aa", value: "1"),
MyObject(name: "bb", value: "2"),
MyObject(name: "cc", value: "3"),
MyObject(name: "dd", value: "4")
])
@State var focus: UUID?
var body: some View {
VStack {
Form {
Text("Header")
ForEach($viewModel.myObjects) { $obj in
FocusField(object: $obj, focus: $focus, nextFocus: {
guard let index = viewModel.myObjects.map( { [=10=].id }).firstIndex(of: obj.id) else {
return
}
focus = viewModel.myObjects.indices.contains(index + 1) ? viewModel.myObjects[index + 1].id : viewModel.myObjects[0].id
})
}
Text("Footer")
}
}
}
}
struct FocusField: View {
@Binding var object: MyObject
@Binding var focus: UUID?
var nextFocus: () -> Void
@FocusState var isFocused: UUID?
var body: some View {
TextField("Test", text: $object.value)
.onChange(of: focus, perform: { newValue in
self.isFocused = newValue
})
.focused(self.$isFocused, equals: object.id)
.onSubmit {
self.nextFocus()
}
}
}
编辑:
此外,按照您的方式在结构中设置 id
是一个非常糟糕的主意。 id 应该是唯一的。它在这里有效,但最佳做法是 UUID
.
第二次编辑:收紧代码。