将SwiftUI列表滚动到新选择

Bra*_*rst 13 macos swiftui

如果您有一个SwiftUI列表,该列表允许单选,则可以通过单击列表(大概使它成为按键响应者),然后使用箭头键来更改选择。如果该选择到达可见区域的末尾,它将滚动整个列表以保持选择可见。

但是,如果选择对象以其他某种方式(例如,使用按钮)更新,则列表不会滚动。

以编程方式设置时,是否有任何方法可以强制列表滚动到新选择?

示例应用程序:

import SwiftUI

struct ContentView: View {
    @State var selection: Int? = 0

    func changeSelection(_ by: Int) {
        switch self.selection {
        case .none:
            self.selection = 0
        case .some(let sel):
            self.selection = max(min(sel + by, 20), 0)
        }
    }

    var body: some View {
        HStack {
            List((0...20), selection: $selection) {
                Text(String($0))
            }
            VStack {
                Button(action: { self.changeSelection(-1) }) {
                    Text("Move Up")
                }
                Button(action: { self.changeSelection(1) }) {
                    Text("Move Down")
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Але*_*кий 5

我尝试了几种解决方案,其中一种是我在我的项目中使用的(我需要对 3 个列表进行水平分页)。这是我的观察:

  1. 我在 SwiftUI 中没有找到任何滚动 List 的方法,文档中还没有提到它;
  2. 您可以尝试 ScrollView(我在下面的变体,这里是其他解决方案),但这些东西可能看起来很奇怪;
  3. 也许最好的方法是使用 UITableView: tutorial from Apple和 tryscrollToRowAtIndexPath方法(就像在这个答案中一样)。

正如我所写,这是我的示例,当然,它需要改进。首先 ScrollView 需要在 GeometryReader 内部,你才能了解内容的真实大小。第二件事是你需要控制你的手势,这可能很困难。最后一个:你需要计算 ScrollViews 内容的当前偏移量,它可能不是我的代码(记住,我试图给你举个例子):

struct ScrollListView: View {

    @State var selection: Int?
    @State private var offset: CGFloat = 0
    @State private var isGestureActive: Bool = false

    func changeSelection(_ by: Int) {
        switch self.selection {
        case .none:
            self.selection = 0
        case .some(let sel):
            self.selection = max(min(sel + by, 30), 0)
        }
    }

    var body: some View {

        HStack {

            GeometryReader { geometry in

                VStack {
                    ScrollView(.vertical, showsIndicators: false) {

                        ForEach(0...29, id: \.self) { line in
                            ListRow(line: line, selection: self.$selection)
                                .frame(height: 20)
                        }

                        }
                    .content.offset(y: self.isGestureActive ? self.offset : geometry.size.height / 4 - CGFloat((self.selection ?? 0) * 20))
                    .gesture(DragGesture()
                        .onChanged({ value in
                            self.isGestureActive = true
                            self.offset = value.translation.width + -geometry.size.width * CGFloat(self.selection ?? 1)

                        })
                    .onEnded({ value in
                        DispatchQueue.main.async { self.isGestureActive = false }
                    }))

                }

            }


            VStack {
                Button(action: { self.changeSelection(-1) }) {
                    Text("Move Up")
                }
                Spacer()
                Button(action: { self.changeSelection(1) }) {
                    Text("Move Down")
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

当然,您需要创建自己的“列表行”:

struct ListRow: View {

    @State var line: Int
    @Binding var selection: Int?

    var body: some View {

        HStack(alignment: .center, spacing: 2){

            Image(systemName: line == self.selection ? "checkmark.square" : "square")
                .padding(.horizontal, 3)
            Text(String(line))
            Spacer()

        }
        .onTapGesture {
            self.selection = self.selection == self.line ? nil : self.line
        }

    }

}
Run Code Online (Sandbox Code Playgroud)

希望它会有所帮助。


Luc*_*uca 4

在适用于 iOs 14 和 MacOs Big Sur 的 SwiftUI 新版本中,他们添加了使用新的 ScrollViewReader 以编程方式滚动到特定单元格的功能:

struct ContentView: View {
    let colors: [Color] = [.red, .green, .blue]

    var body: some View {
        ScrollView {
            ScrollViewReader { value in
                Button("Jump to #8") {
                    value.scrollTo(8)
                }

                ForEach(0..<10) { i in
                    Text("Example \(i)")
                        .frame(width: 300, height: 300)
                        .background(colors[i % colors.count])
                        .id(i)
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

.scrollTo()然后你可以使用这样的方法

value.scrollTo(8, anchor: .top)
Run Code Online (Sandbox Code Playgroud)

图片来源:www.hackingwithswift.com