【问题标题】:SwiftUI automatically scroll to bottom in ScrollView (Bottom first)SwiftUI 在 ScrollView 中自动滚动到底部(底部优先)
【发布时间】:2020-02-11 01:44:41
【问题描述】:

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

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

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

【问题讨论】:

标签: foreach scroll scrollview swiftui swiftui-list


【解决方案1】:

根据 Apple 的documentation on 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)
}

This other article 还展示了如何使用ScrollViewReader 来包装List 的元素,这也很方便。文章中的代码示例:

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) 也没有工作,直到我使用 .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 中这样做会导致它在手动滚动列表时触发列表中的元素。

【讨论】:

    【解决方案2】:

    iOS14 解决方案ScrollViewReader

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

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

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

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

    还有主要的 ContentView:

    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 中运行它。我认为这是一个很大的改进。

    【讨论】:

    • 嘿,你知道每次添加新条目时如何让它滚动吗?我正在制作一个聊天应用,我希望它在每次收到新消息时滚动到底部。
    • 我有同样的问题,当我打开视图时,ForEach 是空的,当我添加内容时,它不会滚动。水平 ScrollView 可以做到吗?
    • @Dam 你需要探索 View.onPreferenceChange
    【解决方案3】:

    更改时滚动到底部

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

    要在ForEach 中的条目数发生变化时滚动到底部,您还可以使用与 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)
                    }
                }
            }
        }
    }
    

    【讨论】:

    • 也许您没有(拥有)足够的声誉,但您应该(d)获得奖牌。非常好的解决方案,避免让元素数组确认为 Hashable(我的数组是 [AnyView]。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-14
    • 1970-01-01
    • 2016-04-26
    • 1970-01-01
    • 1970-01-01
    • 2011-02-19
    相关资源
    最近更新 更多