SwiftUI分页滑动(自动计算Count,转接UIScrollView)

在SwiftUI的ScrollView中似乎找不到isPagingEnabled类似的属性,用于开启分页滑动。

iOS14中用TabView{...}.tabViewStyle(PageTabViewStyle())也可以达到分页滑动的效果,但不知如何获得currentPage之类的属性,无法使用自定义的PageControl

等官方的ScrollViewTabView更新page相关的功能,便无需考虑此转接方案

那么考虑使用UIViewRepresentableUIHostingController等转接UIkit的方案。

大概思路是定义一个PageView,其content传入View,、且有其Coordinator实例作为UIScrollViewDelegate代理监听滑动contentOffset,并且更改绑定的currentPage

本文总结了一个无需传入PageCount的方案

import SwiftUI

struct PageView: UIViewRepresentable {
    @Binding var currentPage: Int
    let contentGetter: () -> [ViewBuilerContent]
    
    init(currentPage: Binding, @MyViewBuilder content: @escaping (() -> [ViewBuilerContent])) {
        _currentPage = currentPage
        contentGetter = content
    }
    
    func makeUIView(context: Context) -> UIScrollView {
        let scrollView = UIScrollView()
        scrollView.contentInsetAdjustmentBehavior = .never
        scrollView.isPagingEnabled = true
        scrollView.delegate = context.coordinator
        scrollView.showsHorizontalScrollIndicator = false
        
        let stackView = UIStackView()
        stackView.alignment = .fill
        stackView.distribution = .fillEqually
        stackView.axis = .horizontal
        stackView.spacing = 0
        
        scrollView.addSubview(stackView)
        
        
        return scrollView
    }
    
    func updateUIView(_ uiView: UIScrollView, context: Context) {
        let contents = self.contentGetter()
        for i in uiView.subviews {
            i.removeFromSuperview()
        }
        let hStack = HStack(alignment: .center, spacing: 0) {
            ForEach(0 ..< contents.count) { i in
                contents[i]
            }
        }
        let contro = UIHostingController(rootView: hStack)
        if let view = contro.view {
            view.backgroundColor = .clear
            uiView.addSubview(view)
            
            view.translatesAutoresizingMaskIntoConstraints = false
            view.topAnchor.constraint(equalTo: uiView.topAnchor).isActive = true
            view.leftAnchor.constraint(equalTo: uiView.leftAnchor).isActive = true
            view.bottomAnchor.constraint(equalTo: uiView.bottomAnchor).isActive = true
            view.rightAnchor.constraint(equalTo: uiView.rightAnchor).isActive = true
            view.heightAnchor.constraint(equalTo: uiView.heightAnchor).isActive = true
            view.widthAnchor.constraint(equalTo: uiView.widthAnchor, multiplier: CGFloat(contents.count)).isActive = true
        }
    }
    
    typealias UIViewType = UIScrollView
    
    func makeCoordinator() -> Coor {
        let coo = Coor(self)
        return coo
    }
    
    class Coor: NSObject, UIScrollViewDelegate {
        var parent: PageView
        init(_ parent: PageView) {
            self.parent = parent
        }
        
        func scrollViewDidScroll(_ scrollView: UIScrollView) {
            let width = scrollView.frame.size.width
            let offsetx = scrollView.contentOffset.x
            let page = offsetx / width + 0.5
            self.parent.currentPage = Int(page)
        }
    }
}

通常在PageView内会传入ForEachView、或for in等,因此用自定义的ResultBuilder特殊处理ForEachView得到View数组,这样有了count

@resultBuilder
enum MyViewBuilder{
    typealias MyContainerView = AnyView
    // MARK: 条件判断
    static func buildArray(_ components: [[Content]]) -> [MyContainerView] {
        return components.flatMap { $0 }.map { MyContainerView($0) }
    }
    static func buildEither(first component: [Content]) -> [MyContainerView] {
        return component.map { MyContainerView($0) }
    }
    static func buildEither(second component: [Content]) -> [MyContainerView] {
        return component.map { MyContainerView($0) }
    }
    static func buildIf(_ content: Content?) -> [MyContainerView] {
        return []
    }
    
    // MARK: 分情况处理不同类型
    static func buildOptional(_ component: [Content]?) -> [MyContainerView] {
        return []
    }
    static func buildExpression(_ expression: Content) -> [MyContainerView] {
        return [MyContainerView(expression)]
    }
    
    // MARK: 把ForEachView拆分
    static func buildExpression(_ expression: ForEach) -> [MyContainerView] {
        return expression.data.map { index in
            let co = expression.content(index)
            return MyContainerView(co)
        }
    }
    
    // MARK: 单个类型
    static func buildBlock(_ c0: [C0]) -> [MyContainerView] {
        var res = [MyContainerView]()
        for i in c0 {
            res.append(MyContainerView(i))
        }
        return res
    }
    
    // MARK: 不同类型集合总体输出
    static func buildBlock(_ c0: [C0], _ c1: [C1]) -> [MyContainerView] {
        var res = [MyContainerView]()
        for i in c0 {
            res.append(MyContainerView(i))
        }
        for i in c1 {
            res.append(MyContainerView(i))
        }
        return res
    }
}

最终在页面声明使用

PageView(currentPage: $currentPage) {
  ForEach(0 ..< 4) { index in
    Color(.red).cornerRadius(10).padding(.horizontal, 10)
  }
}
.frame(height: 360)

附赠一个简单的PageControl

HStack(alignment: .center, spacing: 6) {
  ForEach(0 ..< 4) { index in
    let isCurr = index == currentPage
    Color(isCurr ? .systemBlue : .gray).cornerRadius(2).frame(width: isCurr ? 10 : 4, height: 4)
  }
}
.frame(height: 20).offset(x: 0, y: -10)
截屏2021-08-26 下午3.15.49.png

你可能感兴趣的:(SwiftUI分页滑动(自动计算Count,转接UIScrollView))