有什么办法可以在SwiftUI中制作页面滚动式ScrollView吗?

时间:2019-07-04 19:08:04

标签: uiscrollview swiftui

我一直在浏览每个beta版本的文档,但是还没有找到制作传统的分页ScrollView的方法。我对AppKit不熟悉,所以我想知道SwiftUI中是否不存在此控件,因为它主要是UIKit构造。无论如何,有没有人举这个例子,或者有人可以告诉我这绝对是不可能的,所以我可以停止寻找自己的东西并滚动自己的东西了。

8 个答案:

答案 0 :(得分:4)

不确定这是否对您的问题有所帮助,但是暂时在Apple致力于在SwiftUI中添加分页视图时,我编写了utility library,使您在使用UIPageViewController时拥有SwiftUI的感觉隐藏在引擎盖下。

您可以像这样使用它:

Pages {
    Text("Page 1")
    Text("Page 2")
    Text("Page 3")
    Text("Page 4")
}

或者,如果您的应用程序中有模型列表,则可以这样使用它:

struct Car {
    var model: String
}

let cars = [Car(model: "Ford"), Car(model: "Ferrari")]

ModelPages(cars) { index, car in
    Text("The \(index) car is a \(car.model)")
        .padding(50)
        .foregroundColor(.white)
        .background(Color.blue)
        .cornerRadius(10)
}

答案 1 :(得分:3)

您现在可以使用TabView并将.tabViewStyle设置为PageTabViewStyle()

TabView {
            View1()
            View2()
            View3()
        }
        .tabViewStyle(PageTabViewStyle())

答案 2 :(得分:1)

从Beta 3开始,没有用于页面调度的本机SwiftUI API。我已提出反馈,建议您也这样做。他们将ScrollView API从Beta 2更改为Beta 3,看到进一步的更新我不会感到惊讶。

现在可以包装UIScrollView以便提供此功能。不幸的是,您必须将UIScrollView包装在UIViewController中,并进一步包装在UIViewControllerRepresentable中以支持SwiftUI内容。

Gist here

class UIScrollViewViewController: UIViewController {

    lazy var scrollView: UIScrollView = {
        let v = UIScrollView()
        v.isPagingEnabled = 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())
    }
}

然后使用它:

    var body: some View {
        GeometryReader { proxy in
            UIScrollViewWrapper {
                VStack {
                    ForEach(0..<1000) { _ in
                        Text("Hello world")
                    }
                }
                .frame(width: proxy.size.width) // This ensures the content uses the available width, otherwise it will be pinned to the left
            }
        }
    }

答案 3 :(得分:1)

Apple的官方教程对此进行了举例说明。我觉得很容易理解并且适合我的情况。我真的建议您检查一下并尝试了解如何与UIKit交互。由于SwiftUI太年轻,因此目前还无法涵盖UIKit中的所有功能。与UIKit进行接口应该可以解决大部分需求。

https://developer.apple.com/tutorials/swiftui/interfacing-with-uikit

答案 4 :(得分:1)

结帐SwiftUIPager。这是在SwiftUI本机组件之上构建的寻呼机:

enter image description here

答案 5 :(得分:0)

这里是在swiftUI中使用scrollView的示例,您可以在foreach循环中使用customView并相应地设置垂直或水平滚动行为。试试这个:-

var body: some View {
        VStack {
            ScrollView (alwaysBounceHorizontal: true, showsHorizontalIndicator: false, showsVerticalIndicator: false ) {
                VStack(spacing: 15){
                    ForEach(0...100) {_ in
                        Text("test")
                    }
                }.padding()
            }
        }
    }

答案 6 :(得分:0)

您只需使用.onAppear()跟踪状态即可加载下一页。

struct YourListView : View {

    @ObservedObject var viewModel = YourViewModel()

    let numPerPage = 50

    var body: some View {
        NavigationView {
            List(viewModel.items) { item in
                NavigationLink(destination: DetailView(item: item)) {
                    ItemRow(item: item)
                    .onAppear {
                        if self.shouldLoadNextPage(currentItem: item) {
                            self.viewModel.fetchItems(limitPerPage: self.numPerPage)
                        }
                    }
                }
            }
            .navigationBarTitle(Text("Items"))
            .onAppear {
                guard self.viewModel.items.isEmpty else { return }
                self.viewModel.fetchItems(limitPerPage: self.numPerPage)
            }
        }
    }

    private func shouldLoadNextPage(currentItem item: Item) -> Bool {
        let currentIndex = self.viewModel.items.firstIndex(where: { $0.id == item.id } )
        let lastIndex = self.viewModel.items.count - 1
        let offset = 5 //Load next page when 5 from bottom, adjust to meet needs
        return currentIndex == lastIndex - offset
    }
}

class YourViewModel: ObservableObject {
    @Published private(set) items = [Item]()
    // add whatever tracking you need for your paged API like next/previous and count
    private(set) var fetching = false
    private(set) var next: String?
    private(set) var count = 0


    func fetchItems(limitPerPage: Int = 30, completion: (([Item]?) -> Void)? = nil) {
        // Do your stuff here based on the API rules for paging like determining the URL etc...
        if items.count == 0 || items.count < count {
            let urlString = next ?? "https://somePagedAPI?limit=/(limitPerPage)"
            fetchNextItems(url: urlString, completion: completion)
        } else {
            completion?(pokemon)
        }

    }

    private func fetchNextItems(url: String, completion: (([Item]?) -> Void)?) {
        guard !fetching else { return }
        fetching = true
        Networking.fetchItems(url: url) { [weak self] (result) in
            DispatchQueue.main.async { [weak self] in
                self?.fetching = false
                switch result {
                case .success(let response):
                    if let count = response.count {
                        self?.count = count
                    }
                    if let newItems = response.results {
                        self?.items += newItems
                    }
                    self?.next = response.next
                case .failure(let error):
                    // Error state tracking not implemented but would go here...
                    os_log("Error fetching data: %@", error.localizedDescription)
                }
            }
        }
    }
}

进行修改以适合您正在调用的任何API,并根据您的应用程序体系结构处理错误。

答案 7 :(得分:0)

如果您想利用PageTabViewStyle中新的TabView,但需要垂直分页滚动视图,则可以使用.rotationEffect()之类的效果修饰符。

使用此方法,我编写了一个名为libraryVerticalTabView ?,只需将现有的TabView更改为VTabView即可使TabView垂直。