如何才能在不同子视图中使用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?

查看:116
本文介绍了如何才能在不同子视图中使用TextFields更改SwiftUI应用程序的FocusState,而不需要刷新视图,从而导致反弹效果?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的问题:我希望用户能够在不弹出视图的情况下从TextField转到TextField,如下面的gif所示。

我的用例:我在多个子视图中有多个TextField和TextEditor。这些TextField是动态生成的,所以我希望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 app 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: $0) 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时,您确实应该处于ScrollViewForm或其他类型的贪婪视图中。即使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( { $0.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

第二次编辑:已收紧代码。

这篇关于如何才能在不同子视图中使用TextFields更改SwiftUI应用程序的FocusState,而不需要刷新视图,从而导致反弹效果?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆