There doesn’t seem to be a property like isPagingEnabled in SwiftUI’s ScrollView to enable paging.

Using TabView iOS14 {… }.tabViewStyle(PageTabViewStyle()) can also work as a paging slide, but you don’t know how to get properties like currentPage and you can’t use custom PageControl.

Wait for the official ScrollView or TabView to update page-related functionality, then you don’t need to consider this switch

Then consider using UIViewRepresentable, UIHostingController, etc., to switch to UIkit.

Define a PageView whose content is passed to UIScrollViewRep. UIScrollViewRep is the layout of UIScrollView inside UIScrollViewRep. And has its Coordinator instance as a UIScrollViewDelegate agent listening for sliding contentOffset and changing the currentPage of the binding

This article summarizes a scenario that does not require passing in PageCount

import SwiftUI struct PageView<ViewBuilerContent: View>: UIViewRepresentable { @Binding var currentPage: Int let contentGetter: () -> [ViewBuilerContent] init(currentPage: Binding<Int>, @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<ViewBuilerContent> { let coo = Coor<ViewBuilerContent>(self) return coo } class Coor<ViewBuilerContent: View>: NSObject, UIScrollViewDelegate { var parent: PageView<ViewBuilerContent> init(_ parent: PageView<ViewBuilerContent>) { 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)}}Copy the code

Usually in PageView will infect ForEachView, or for in, etc., so use custom ResultBuilder special processing ForEachView to get the View array, so there is count

@resultBuilder enum MyViewBuilder{typeAlias MyContainerView = AnyView View>(_ components: [[Content]]) -> [MyContainerView] { return components.flatMap { $0 }.map { MyContainerView($0) } } static func buildEither<Content: View>(first component: [Content]) -> [MyContainerView] { return component.map { MyContainerView($0) } } static func buildEither<Content: View>(second component: [Content]) -> [MyContainerView] { return component.map { MyContainerView($0) } } static func buildIf<Content: View>(_ content: Content?) -> [MyContainerView] {return []} static func buildOptional<Content: View>(_ component: [Content]?) -> [MyContainerView] { return [] } static func buildExpression<Content: View>(_ expression: Content) -> [MyContainerView] { return [MyContainerView(expression)] } // MARK: Static func buildExpression<C0, C1, Content: View>(_ expression: ForEach<C0, C1, Content>) -> [MyContainerView] { return expression.data.map { index in let co = expression.content(index) return // MARK: static func buildBlock<C0: View>(_ C0: View) [C0]) -> [MyContainerView] { var res = [MyContainerView]() for i in c0 { res.append(MyContainerView(i)) } return res } Static func buildBlock<C0: View, C1: View>(_ 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 } }Copy the code

Finally declare use on the page

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

Custom 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)Copy the code