23

我的问题是我(在 SwiftUI 中)有一个 ScrollView,里面有一个 foreach。知道当 foreach 加载我的所有条目时,我希望最后一个条目是焦点。

我做了一些谷歌研究,但我没有找到任何答案。

  ScrollView {
    VStack {
      ForEach (0..<self.entries.count) { index in
        Group {
          Text(self.entries[index].getName())
        }
      }
    }
  }
4

3 回答 3

22

更改时滚动到底部

我还没有足够的声望来发表评论,所以你去@Dam 和@Evert

要在更改中的条目数时滚动到底部,您ForEach还可以使用与 a 相同的方法ScrollViewReader,如上面的答案中所述,通过添加视图修饰符onChange,如下所示:

struct ContentView: View {
    let colors: [Color] = [.red, .blue, .green]
    var entries: [Entry] = Array(repeating: Entry(), count: 10)

    var body: some View {
        ScrollView {
            ScrollViewReader { value in
                            
                ForEach(0..<entries.count) { i in
                    Text(self.entries[i].getName())
                        .frame(width: 300, height: 200)
                        .background(colors[i % colors.count])
                        .padding(.all, 20)
                }
                .onChange(of: entries.count) { _ in
                    value.scrollTo(entries.count - 1)
                }
            }
        }
    }
}
于 2020-12-20T16:06:12.443 回答
20

iOS14 的解决方案ScrollViewReader

在 iOS14 中,SwiftUI 获得了新的能力。为了能够在 ScrollView 中滚动到具有给定id. 有一种名为的新类型ScrollViewReader,其工作方式与Geometry Reader.

下面的代码将滚动到视图中的最后一项。

所以这是你的'Entry'结构我猜:

struct Entry {
    let id = UUID()
    
    func getName() -> String {
         return "Entry with id \(id.uuidString)"
    }
}

和主要的内容视图:

struct ContentView: View {
    var entries: [Entry] = Array(repeating: Entry(), count: 10)

    var body: some View {
        ScrollView {
            ScrollViewReader { value in
                ForEach(entries, id: \.id) { entry in
                    Text(entry.getName())
                        .frame(width: 300, height: 200)
                        .padding(.all, 20)
                }
                .onAppear {
                    value.scrollTo(entries.last?.id, anchor: .center)
                }
            }
        }
    }
}

尝试在 WWDC20 上宣布的新版 SwiftUI 中运行它。我认为这是一个很大的改进。

于 2020-07-03T16:50:11.053 回答
7

根据 Apple 关于 的文档ScrollViewReader,它应该包装滚动视图,而不是嵌套在其中。

Apple 文档中的示例:

@Namespace var topID
@Namespace var bottomID

var body: some View {
    ScrollViewReader { proxy in
        ScrollView {
            Button("Scroll to Bottom") {
                withAnimation {
                    proxy.scrollTo(bottomID)
                }
            }
            .id(topID)

            VStack(spacing: 0) {
                ForEach(0..<100) { i in
                    color(fraction: Double(i) / 100)
                        .frame(height: 32)
                }
            }

            Button("Top") {
                withAnimation {
                    proxy.scrollTo(topID)
                }
            }
            .id(bottomID)
        }
    }
}

func color(fraction: Double) -> Color {
    Color(red: fraction, green: 1 - fraction, blue: 0.5)
}

这篇另外一篇文章也展示了如何使用ScrollViewReaderwrap a Listof 元素,也可以很方便。文章中的代码示例:

ScrollViewReader { scrollView in
    List {
        ForEach(photos.indices) { index in
            Image(photos[index])
                .resizable()
                .scaledToFill()
                .cornerRadius(25)
                .id(index)
        }
    }
 
    .
    .
    .
}

对我来说,使用value.scrollTo(entries.count - 1)不起作用。此外,value.scrollTo(entries.last?.id)在我使用 view 修饰符之前 using 也不起作用 .id(...)(可能是因为我的条目不符合Identifiable)。

使用ForEach,这是我正在处理的代码:

ScrollViewReader { value in
    ScrollView {
        ForEach(state.messages, id: \.messageId) { message in
            MessageView(message: message)
                .id(message.messageId)
        }
    }
    .onAppear {
        value.scrollTo(state.messages.last?.messageId)
    }
    .onChange(of: state.messages.count) { _ in
        value.scrollTo(state.messages.last?.messageId)
    }
}

使用时的一个重要考虑因素onAppear是在ScrollView/List而不是在ForEach. 这样做ForEach会导致它在手动滚动列表时触发列表中的元素。

于 2021-05-27T21:52:54.063 回答