【问题标题】:How to scroll to position UIScrollView in Wrapper for SwiftUI?如何在 SwiftUI 的 Wrapper 中滚动定位 UIScrollView?
【发布时间】:2021-08-21 10:27:11
【问题描述】:

我有一个来自 UIKit 的 ScrollView 并将其用于 SwiftUI:Is there any way to make a paged ScrollView in SwiftUI?

问题:如何在 UIScrollView 中滚动到某个位置,只需单击 SwiftUI 视图中的按钮,或者在第一次显示滚动视图

我尝试了 contentOffset 但这没有用。也许我做错了什么。

ScrollViewWrapper:

class UIScrollViewViewController: UIViewController {
    lazy var scrollView: UIScrollView = {
        let v = UIScrollView()
        v.isPagingEnabled = false
        v.alwaysBounceVertical = true
        return v
    }()

    var hostingController: UIHostingController<AnyView> = UIHostingController(rootView: AnyView(EmptyView()))

    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.addSubview(self.scrollView)
        self.pinEdges(of: self.scrollView, to: self.view)

        self.hostingController.willMove(toParent: self)
        self.scrollView.addSubview(self.hostingController.view)
        self.pinEdges(of: self.hostingController.view, to: self.scrollView)
        self.hostingController.didMove(toParent: self)

    }

    func pinEdges(of viewA: UIView, to viewB: UIView) {
        viewA.translatesAutoresizingMaskIntoConstraints = false
        viewB.addConstraints([
        viewA.leadingAnchor.constraint(equalTo: viewB.leadingAnchor),
        viewA.trailingAnchor.constraint(equalTo: viewB.trailingAnchor),
        viewA.topAnchor.constraint(equalTo: viewB.topAnchor),
        viewA.bottomAnchor.constraint(equalTo: viewB.bottomAnchor),
        ])
    }

}

struct UIScrollViewWrapper<Content: View>: UIViewControllerRepresentable {

    var content: () -> Content

    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
    }

    func makeUIViewController(context: Context) -> UIScrollViewViewController {
        let vc = UIScrollViewViewController()
        vc.hostingController.rootView = AnyView(self.content())
        return vc
    }

    func updateUIViewController(_ viewController: UIScrollViewViewController, context: Context) {
        viewController.hostingController.rootView = AnyView(self.content())
    }
}

SwiftUI 使用:

struct ContentView: View{
    @ObservedObject var search = SearchBar()
    var body: some View{
       NavigationView{
        GeometryReader{geo in
            UIScrollViewWrapper{      //<-----------------
                VStack{
                    ForEach(0..<10){i in
                        Text("lskdfj")
                    }
                }
                .frame(width: geo.size.width)
            }
            .navigationBarTitle("Test")
        }
       }
    }
}

【问题讨论】:

  • 为什么选择 UIKit 版本?有 SwiftUI 版本。
  • 如果在带有标题的导航视图中包含滚动视图,则会出现错误。有时它会跳跃。
  • 我在 NavigationView 中使用了 ScrollView,它有标题。它不会跳。
  • 使用 UITableView 代替 UISscrollView。
  • 我的意思是类似于这篇文章stackoverflow.com/questions/64280447/…

标签: swiftui uikit


【解决方案1】:

我们将首先在UIViewControllerRepresentable 中声明offset 属性,使用propertyWrapper @Binding,因为它的值可以被scrollview 或父视图(ContentView)更改。

struct UIScrollViewWrapper<Content: View>: UIViewControllerRepresentable {
    var content: () -> Content
    @Binding var offset: CGPoint
    init(offset: Binding<CGPoint>, @ViewBuilder content: @escaping () -> Content) {
        self.content = content
        _offset = offset
    }
// ....//
}

如果偏移量变化是由于父视图的原因,我们必须将这些变化应用到updateUIViewController函数中的scrollView(当视图状态改变时调用):

func updateUIViewController(_ viewController: UIScrollViewViewController, context: Context) {
    viewController.hostingController.rootView = AnyView(content())
    viewController.scrollView.contentOffset = offset
}

当偏移量因用户滚动而改变时,我们必须在Binding 上反映这一变化。为此,我们必须声明一个Coordinator,这将是一个UIScrollViewDelegate,并在其scrollViewDidScroll 函数中修改偏移量:

class Controller: NSObject, UIScrollViewDelegate {
    var parent: UIScrollViewWrapper<Content>
    init(parent: UIScrollViewWrapper<Content>) {
        self.parent = parent
    }

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        parent.offset = scrollView.contentOffset
    }
}

并且,在struct UIScrollViewWrapper&lt;Content: View&gt;: UIViewControllerRepresentable

func makeCoordinator() -> Controller {
    return Controller(parent: self)
}

最后,对于初始偏移量(这很重要,否则您的起始偏移量将始终为 0),这发生在 makeUIViewController: 你必须添加这些行:

vc.view.layoutIfNeeded ()
vc.scrollView.contentOffset = offset

最终项目:

import SwiftUI

struct ContentView: View {
    @State private var offset: CGPoint = CGPoint(x: 0, y: 200)
    let texts: [String] = (1...100).map {_ in String.random(length: Int.random(in: 6...20))}
    var body: some View {
        ZStack(alignment: .top) {
            GeometryReader { geo in
                UIScrollViewWrapper(offset: $offset) { //
                    VStack {
                        Text("Start")
                            .foregroundColor(.red)
                        ForEach(texts, id: \.self) { text in
                            Text(text)
                        }
                    }
                        .padding(.top, 40)
                    
                    .frame(width: geo.size.width)
                }
                .navigationBarTitle("Test")
                
            }
            HStack {
                Text(offset.debugDescription)
                Button("add") {
                    offset.y += 100
                }
            }
            .padding(.bottom, 10)
            .frame(maxWidth: .infinity)
            .background(Color.white)
        }
    }
}

class UIScrollViewViewController: UIViewController {
    lazy var scrollView: UIScrollView = {
        let v = UIScrollView()
        v.isPagingEnabled = false
        v.alwaysBounceVertical = true
        return v
    }()

    var hostingController: UIHostingController<AnyView> = UIHostingController(rootView: AnyView(EmptyView()))

    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(scrollView)
        pinEdges(of: scrollView, to: view)

        hostingController.willMove(toParent: self)
        scrollView.addSubview(hostingController.view)
        pinEdges(of: hostingController.view, to: scrollView)
        hostingController.didMove(toParent: self)
    }

    func pinEdges(of viewA: UIView, to viewB: UIView) {
        viewA.translatesAutoresizingMaskIntoConstraints = false
        viewB.addConstraints([
            viewA.leadingAnchor.constraint(equalTo: viewB.leadingAnchor),
            viewA.trailingAnchor.constraint(equalTo: viewB.trailingAnchor),
            viewA.topAnchor.constraint(equalTo: viewB.topAnchor),
            viewA.bottomAnchor.constraint(equalTo: viewB.bottomAnchor),
        ])
    }
}

struct UIScrollViewWrapper<Content: View>: UIViewControllerRepresentable {
    var content: () -> Content
    @Binding var offset: CGPoint
    init(offset: Binding<CGPoint>, @ViewBuilder content: @escaping () -> Content) {
        self.content = content
        _offset = offset
    }

    func makeCoordinator() -> Controller {
        return Controller(parent: self)
    }

    func makeUIViewController(context: Context) -> UIScrollViewViewController {
        let vc = UIScrollViewViewController()
        vc.scrollView.contentInsetAdjustmentBehavior = .never
        vc.hostingController.rootView = AnyView(content())
        vc.view.layoutIfNeeded()
        vc.scrollView.contentOffset = offset
        vc.scrollView.delegate = context.coordinator
        return vc
    }

    func updateUIViewController(_ viewController: UIScrollViewViewController, context: Context) {
        viewController.hostingController.rootView = AnyView(content())
        viewController.scrollView.contentOffset = offset
    }

    class Controller: NSObject, UIScrollViewDelegate {
        var parent: UIScrollViewWrapper<Content>
        init(parent: UIScrollViewWrapper<Content>) {
            self.parent = parent
        }

        func scrollViewDidScroll(_ scrollView: UIScrollView) {
            parent.offset = scrollView.contentOffset
        }
    }
}

【讨论】:

    【解决方案2】:

    您需要将@Binding var offset: CGPoint 传递给UIScrollViewWrapper,然后在您的SwiftUI 视图中单击按钮时,您可以更新绑定值,然后可以在UIViewControllerRepresentable 的更新方法中使用该值。另一个想法是改用UIViewRepresentable 并将其与UIScrollView 一起使用。这是一篇有用的文章并设置了它的偏移量:https://www.fivestars.blog/articles/scrollview-offset/

    【讨论】:

    • 谢谢。我尝试了第一种方法,它奏效了。在我的第一次尝试中,我通过控制器将绑定绑定到另一个不起作用的控制器
    • 我希望滚动视图从某个特定点开始。我不知道该怎么做。有时滚动视图会更新位置,有时不会。如何正确处理变量?如果我可能让 contentOffset 的值为 y: 100 在 makeUIController 屏幕上没有偏移
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-10-24
    • 1970-01-01
    • 2020-04-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多