带有 @FocusState 和焦点更改处理的 SwiftUI 列表

Ser*_*gey 6 swiftui

我想使用List,@FocusState来跟踪焦点,并.onChanged(of: focus)确保当前聚焦的字段通过 可见ScrollViewReader。问题是:当所有内容都设置在一起时,List滚动过程中会不断重建,从而使滚动不那么平滑。

我发现当List我附加时会在滚动时重建.onChanged(of: focus)List如果我替换为,问题就消失了ScrollView,但我喜欢 的外观List,我需要部分支持,并且我需要编辑功能(例如删除、移动项目),所以我需要坚持查看List

我用它Self._printChanges()来看看是什么让主体在滚动时重建自身,输出如下:

ContentView: _focus changed.
ContentView: _focus changed.
ContentView: _focus changed.
ContentView: _focus changed.
...
Run Code Online (Sandbox Code Playgroud)

附在 上的封条上没有打印任何内容.onChanged(of: focus)。下面是简化的示例,在此示例中滚动的平滑度不是问题,但是,一旦列表内容或多或少复杂,平滑滚动就会消失,这实际上是由于.onChanged(of: focus):(

问题:是否有机会监听焦点变化而不引起列表在滚动时重建自身?

struct ContentView: View {
    enum Field: Hashable {
        case fieldId(Int)
    }
    
    @FocusState var focus: Field?
    @State var text: String = ""
    
    var body: some View {
        List {
            let _ = Self._printChanges()
            ForEach(0..<100) {
                TextField("Enter the text for \($0)", text: $text)
                    .id(Field.fieldId($0))
                    .focused($focus, equals: .fieldId($0))
            }
        }
        .onChange(of: focus) { _ in
            print("Not printed unless focused manually")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Asp*_*eri 2

我建议考虑将列表行内容分离到独立视图中,并使用诸如焦点“选择”方法之类的方法。每行的内部FocusState可以防止父视图进行不必要的更新(我认为类似于预“设置”)。

使用 Xcode 13.4 / iOS 15.5 进行测试

struct ContentView: View {

    enum Field: Hashable {
        case fieldId(Int)
    }

    @State private var inFocus: Field?

    var body: some View {
        List {
            let _ = Self._printChanges()
            ForEach(0..<100, id: \.self) {
                ExtractedView(i: $0, inFocus: $inFocus)
            }
        }
        .onChange(of: inFocus) { _ in
            print("Not printed unless focused manually")
        }
    }

    struct ExtractedView: View {
        let i: Int
        @Binding var inFocus: Field?

        @State private var text: String = ""
        @FocusState private var focus: Bool     // << internal !!

        var body: some View {
            TextField("Enter the text for \(i)", text: $text)
                .focused($focus)
                .id(Field.fieldId(i))
                .onChange(of: focus) { _ in
                    inFocus = .fieldId(i)     // << report selection outside
                }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)