看起来在当前的工具/系统中,刚刚发布的Xcode 11.4/iOS 13.4将不会有SwiftUI本机支持List中的"滚动到"功能.所以,即使他们,苹果,在下一个major版本中提供,我也需要对iOS 13的向后支持.x.

那么,我将如何在最简单的&光路?

  • 将列表滚动到末尾
  • 将列表滚动到顶部
  • 以及其他

(我不喜欢像之前提议的那样,将UITableView个基础设施全部打包成UIViewRepresentable/UIViewControllerRepresentable个).

推荐答案

SWIFTUI 2.0

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

Note: Row content design is out of consideration scope

使用Xcode 12b/iOS 14进行测试

demo2

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+

这是一种简化的方法,它可以工作,看起来很合适,并且需要几个屏幕代码.

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

Demo of usage:

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
                        )
                }
            }
        }
    }
}

demo

Solution:

将Light view representable注入到List中可以访问UIKit的视图层次 struct .当List重复使用行时,没有更多的值将行放入屏幕.

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(需要做一次),然后将所需的"滚动到"操作重定向到存储的滚动视图

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
    }
}

Swift相关问答推荐

传递给SWIFT ResultBuilder的闭包中结果类型的对象

将`@Environment`值赋给`Binding`参数

如何使用变量 Select 哪个向量(?)在我的 struct 中密谋吗?

如何才能在同一线程上调用类中的每个方法,而不对每个调用使用同步块?

SwiftUI-如何使用剩余时间制作倒计时计时器

Swift图表';chartXVisibleDomain挂起或崩溃

为什么 UITapGestureRecognizer 对于 Swift 集合视图中的单元格图像无法正常工作?

Swift:结果的失败类型不能是协议 - Type 'any ShadowError' cannot conform to Error

如何将视图添加到多行文本视图的末尾?

是否有一个带有空初始值设定项的 Swift 类/ struct 的标准协议

Swiftui 无法从核心数据中获取数据

TextField 键入未完成

快速递归:函数与闭包

XCUITest 在 TearDown 期间随机失败-无法终止 com.bundle.id

为什么 Apple Scrumdinger Sample App 使用两个事实来源?

如何在填充上添加 if else 语句? SwiftUI

SwiftUI - 呈现工作表后导航栏按钮不可点击

自定义 MKAnnotation 标注视图?

用 UIBezierPath 画一条线

从 Swift 初始化程序调用方法