如何在 SwiftUI 中以编程方式滚动列表? [英] How to scroll List programmatically in SwiftUI?

查看:26
本文介绍了如何在 SwiftUI 中以编程方式滚动列表?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

看起来在当前的工具/系统中,刚刚发布的 Xcode 11.4/iOS 13.4,List 中将没有 SwiftUI 原生支持滚动到"功能.因此,即使他们,Apple,将在下一个主要发布中提供它,我将需要对 iOS 13.x 的向后支持.

It looks like in current tools/system, just released Xcode 11.4 / iOS 13.4, there will be no SwiftUI-native support for "scroll-to" feature in List. So even if they, Apple, will provide it in next major released, I will need backward support for iOS 13.x.

那么我将如何以最简单的方式做到这一点 &轻方式?

So how would I do it in most simple & light way?

  • 滚动列表到最后
  • 将列表滚动到顶部
  • 和其他

(我不喜欢将完整的 UITableView 基础结构包装到 UIViewRepresentable/UIViewControllerRepresentable 中,就像之前在 SO 上提出的那样).

(I don't like wrapping full UITableView infrastructure into UIViewRepresentable/UIViewControllerRepresentable as was proposed earlier on SO).

推荐答案

SWIFTUI 2.0

这是 Xcode 12/iOS 14 (SwiftUI 2.0) 中可能的替代解决方案,当滚动控件位于滚动区域之外时,可以在同一场景中使用(因为 SwiftUI2 ScrollViewReader 只能使用 inside ScrollView)

Here is possible alternate solution in Xcode 12 / iOS 14 (SwiftUI 2.0) that can be used in same scenario when controls for scrolling is outside of scrolling area (because SwiftUI2 ScrollViewReader can be used only inside ScrollView)

注意:行内容设计不在考虑范围内

使用 Xcode 12b/iOS 14 测试

Tested with Xcode 12b / iOS 14

class ScrollToModel: ObservableObject {
    enum Action {
        case end
        case top
    }
    @Published var direction: Action? = nil
}

struct ContentView: View {
    @StateObject var vm = ScrollToModel()

    let items = (0..<200).map { $0 }
    var body: some View {
        VStack {
            HStack {
                Button(action: { vm.direction = .top }) { // < here
                    Image(systemName: "arrow.up.to.line")
                      .padding(.horizontal)
                }
                Button(action: { vm.direction = .end }) { // << here
                    Image(systemName: "arrow.down.to.line")
                      .padding(.horizontal)
                }
            }
            Divider()
            
            ScrollViewReader { sp in
                ScrollView {
               
                    LazyVStack {
                        ForEach(items, id: .self) { item in
                            VStack(alignment: .leading) {
                                Text("Item (item)").id(item)
                                Divider()
                            }.frame(maxWidth: .infinity).padding(.horizontal)
                        }
                    }.onReceive(vm.$direction) { action in
                        guard !items.isEmpty else { return }
                        withAnimation {
                            switch action {
                                case .top:
                                    sp.scrollTo(items.first!, anchor: .top)
                                case .end:
                                    sp.scrollTo(items.last!, anchor: .bottom)
                                default:
                                    return
                            }
                        }
                    }
                }
            }
        }
    }
}

SWIFTUI 1.0+

这是一种有效的方法的简化变体,看起来很合适,并且需要几个屏幕代码.

Here is simplified variant of approach that works, looks appropriate, and takes a couple of screens code.

使用 Xcode 11.2+/iOS 13.2+ 测试(也使用 Xcode 12b/iOS 14)

Tested with Xcode 11.2+ / iOS 13.2+ (also with Xcode 12b / iOS 14)

使用演示:

struct ContentView: View {
    private let scrollingProxy = ListScrollingProxy() // proxy helper

    var body: some View {
        VStack {
            HStack {
                Button(action: { self.scrollingProxy.scrollTo(.top) }) { // < here
                    Image(systemName: "arrow.up.to.line")
                      .padding(.horizontal)
                }
                Button(action: { self.scrollingProxy.scrollTo(.end) }) { // << here
                    Image(systemName: "arrow.down.to.line")
                      .padding(.horizontal)
                }
            }
            Divider()
            List {
                ForEach(0 ..< 200) { i in
                    Text("Item (i)")
                        .background(
                           ListScrollingHelper(proxy: self.scrollingProxy) // injection
                        )
                }
            }
        }
    }
}

解决方案:

Light 视图可表示被注入到 List 中,可以访问 UIKit 的视图层次结构.由于 List 重用行,因此没有更多的值可以将行放入屏幕.

Light view representable being injected into List gives access to UIKit's view hierarchy. As List reuses rows there are no more values then fit rows into screen.

struct ListScrollingHelper: UIViewRepresentable {
    let proxy: ListScrollingProxy // reference type

    func makeUIView(context: Context) -> UIView {
        return UIView() // managed by SwiftUI, no overloads
    }

    func updateUIView(_ uiView: UIView, context: Context) {
        proxy.catchScrollView(for: uiView) // here UIView is in view hierarchy
    }
}

找到封闭的 UIScrollView(需要做一次)然后重定向所需的滚动到"的简单代理;对该存储的滚动视图的操作

Simple proxy that finds enclosing UIScrollView (needed to do once) and then redirects needed "scroll-to" actions to that stored scrollview

class ListScrollingProxy {
    enum Action {
        case end
        case top
        case point(point: CGPoint)     // << bonus !!
    }

    private var scrollView: UIScrollView?

    func catchScrollView(for view: UIView) {
        if nil == scrollView {
            scrollView = view.enclosingScrollView()
        }
    }

    func scrollTo(_ action: Action) {
        if let scroller = scrollView {
            var rect = CGRect(origin: .zero, size: CGSize(width: 1, height: 1))
            switch action {
                case .end:
                    rect.origin.y = scroller.contentSize.height +
                        scroller.contentInset.bottom + scroller.contentInset.top - 1
                case .point(let point):
                    rect.origin.y = point.y
                default: {
                    // default goes to top
                }()
            }
            scroller.scrollRectToVisible(rect, animated: true)
        }
    }
}

extension UIView {
    func enclosingScrollView() -> UIScrollView? {
        var next: UIView? = self
        repeat {
            next = next?.superview
            if let scrollview = next as? UIScrollView {
                return scrollview
            }
        } while next != nil
        return nil
    }
}

这篇关于如何在 SwiftUI 中以编程方式滚动列表?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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