How can I implement PageView in SwiftUI?

后端 未结 4 537
被撕碎了的回忆
被撕碎了的回忆 2020-11-29 09:52

I am new to SwiftUI. I have three views and I want them in a PageView. I want to move each Views by swipe like a pageview and I want the little dots to indicate in which vie

相关标签:
4条回答
  • 2020-11-29 10:04

    For apps that target iOS 14 and later, the answer suggested by @pawello2222 should be considered the correct one. I have tried it in two apps now and it works great, with very little code.

    I have wrapped the proposed concept in a struct that can be provided with both views as well as with an item list and a view builder. It can be found here. The code looks like this:

    @available(iOS 14.0, *)
    public struct MultiPageView: View {
        
        public init<PageType: View>(
            pages: [PageType],
            indexDisplayMode: PageTabViewStyle.IndexDisplayMode = .automatic,
            currentPageIndex: Binding<Int>) {
            self.pages = pages.map { $0.any() }
            self.indexDisplayMode = indexDisplayMode
            self.currentPageIndex = currentPageIndex
        }
        
        public init<Model, ViewType: View>(
            items: [Model],
            indexDisplayMode: PageTabViewStyle.IndexDisplayMode = .automatic,
            currentPageIndex: Binding<Int>,
            pageBuilder: (Model) -> ViewType) {
            self.pages = items.map { pageBuilder($0).any() }
            self.indexDisplayMode = indexDisplayMode
            self.currentPageIndex = currentPageIndex
        }
        
        private let pages: [AnyView]
        private let indexDisplayMode: PageTabViewStyle.IndexDisplayMode
        private var currentPageIndex: Binding<Int>
        
        public var body: some View {
            TabView(selection: currentPageIndex) {
                ForEach(Array(pages.enumerated()), id: \.offset) {
                    $0.element.tag($0.offset)
                }
            }
            .tabViewStyle(PageTabViewStyle(indexDisplayMode: indexDisplayMode))
        }
    }
    
    0 讨论(0)
  • 2020-11-29 10:05

    The easiest way to do this is via iPages.

    import SwiftUI
    import iPages
    
    struct ContentView: View {
        @State var currentPage = 0
        var body: some View {
            iPages(currentPage: $currentPage) {
                Text("                                                                    
    0 讨论(0)
  • 2020-11-29 10:17

    Page Control

    struct PageControl: UIViewRepresentable {
        var numberOfPages: Int
        @Binding var currentPage: Int
        func makeCoordinator() -> Coordinator {
            Coordinator(self)
        }
    
        func makeUIView(context: Context) -> UIPageControl {
            let control = UIPageControl()
            control.numberOfPages = numberOfPages
            control.pageIndicatorTintColor = UIColor.lightGray
            control.currentPageIndicatorTintColor = UIColor.darkGray
            control.addTarget(
                context.coordinator,
                action: #selector(Coordinator.updateCurrentPage(sender:)),
                for: .valueChanged)
    
            return control
        }
    
        func updateUIView(_ uiView: UIPageControl, context: Context) {
            uiView.currentPage = currentPage
        }
    
        class Coordinator: NSObject {
            var control: PageControl
    
            init(_ control: PageControl) {
                self.control = control
            }
            @objc
            func updateCurrentPage(sender: UIPageControl) {
                control.currentPage = sender.currentPage
            }
        }
    }
    

    Your page View

    struct PageView<Page: View>: View {
        var viewControllers: [UIHostingController<Page>]
        @State var currentPage = 0
        init(_ views: [Page]) {
            self.viewControllers = views.map { UIHostingController(rootView: $0) }
        }
    
        var body: some View {
            ZStack(alignment: .bottom) {
                PageViewController(controllers: viewControllers, currentPage: $currentPage)
                PageControl(numberOfPages: viewControllers.count, currentPage: $currentPage)
            }
        }
    }
    

    Your page View Controller

    
    struct PageViewController: UIViewControllerRepresentable {
        var controllers: [UIViewController]
        @Binding var currentPage: Int
    
        func makeCoordinator() -> Coordinator {
            Coordinator(self)
        }
    
        func makeUIViewController(context: Context) -> UIPageViewController {
            let pageViewController = UIPageViewController(
                transitionStyle: .scroll,
                navigationOrientation: .horizontal)
            pageViewController.dataSource = context.coordinator
            pageViewController.delegate = context.coordinator
    
            return pageViewController
        }
    
        func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
            guard !controllers.isEmpty else {
                return
            }
            context.coordinator.parent = self
            pageViewController.setViewControllers(
                [controllers[currentPage]], direction: .forward, animated: true)
        }
    
        class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
            var parent: PageViewController
    
            init(_ pageViewController: PageViewController) {
                self.parent = pageViewController
            }
    
            func pageViewController(
                _ pageViewController: UIPageViewController,
                viewControllerBefore viewController: UIViewController) -> UIViewController? {
                guard let index = parent.controllers.firstIndex(of: viewController) else {
                    return nil
                }
                if index == 0 {
                    return parent.controllers.last
                }
                return parent.controllers[index - 1]
            }
    
            func pageViewController(
                _ pageViewController: UIPageViewController,
                viewControllerAfter viewController: UIViewController) -> UIViewController? {
                guard let index = parent.controllers.firstIndex(of: viewController) else {
                    return nil
                }
                if index + 1 == parent.controllers.count {
                    return parent.controllers.first
                }
                return parent.controllers[index + 1]
            }
    
            func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
                if completed,
                    let visibleViewController = pageViewController.viewControllers?.first,
                    let index = parent.controllers.firstIndex(of: visibleViewController) {
                    parent.currentPage = index
                }
            }
        }
    }
    

    Let's say you have a view like

    struct CardView: View {
        var album: Album
        var body: some View {
            URLImage(URL(string: album.albumArtWork)!)
                .resizable()
                .aspectRatio(3 / 2, contentMode: .fit)
        }
    }
    

    You can use this component in your main SwiftUI view like this.

    PageView(vM.Albums.map { CardView(album: $0) }).frame(height: 250)
    
    0 讨论(0)
  • 2020-11-29 10:17

    SwiftUI 2

    There is now a native equivalent of UIPageViewController in SwiftUI 2 / iOS 14.

    To create a paged view, add the .tabViewStyle modifier to TabView and pass PageTabViewStyle.

    @main
    struct TestApp: App {
        var body: some Scene {
            WindowGroup {
                TabView {
                    FirstView()
                    SecondView()
                    ThirdView()
                }
                .tabViewStyle(PageTabViewStyle())
            }
        }
    }
    

    You can also control how the paging dots are displayed:

    // hide paging dots
    .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
    

    You can find a more detailed explanation in this link:

    • How to create scrolling pages of content using tabViewStyle()

    Vertical variant

    TabView {
        Group {
            FirstView()
            SecondView()
            ThirdView()
        }
        .rotationEffect(Angle(degrees: -90))
    }
    .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
    .rotationEffect(Angle(degrees: 90))
    

    Custom component

    If you're tired of passing tabViewStyle every time you can create your own PageView:

    struct PageView<SelectionValue, Content>: View where SelectionValue: Hashable, Content: View {
        @State private var selectionInternal: SelectionValue
        @Binding private var selectionExternal: SelectionValue
        private let indexDisplayMode: PageTabViewStyle.IndexDisplayMode
        private let indexBackgroundDisplayMode: PageIndexViewStyle.BackgroundDisplayMode
        private let content: () -> Content
    
        init(
            selection: Binding<SelectionValue>,
            indexDisplayMode: PageTabViewStyle.IndexDisplayMode = .automatic,
            indexBackgroundDisplayMode: PageIndexViewStyle.BackgroundDisplayMode = .automatic,
            @ViewBuilder content: @escaping () -> Content
        ) {
            self._selectionInternal = .init(initialValue: selection.wrappedValue)
            self._selectionExternal = selection
            self.indexDisplayMode = indexDisplayMode
            self.indexBackgroundDisplayMode = indexBackgroundDisplayMode
            self.content = content
        }
    
        var body: some View {
            TabView(selection: $selectionInternal) {
                content()
            }
            .onChange(of: selectionInternal) {
                selectionExternal = $0
            }
            .tabViewStyle(PageTabViewStyle(indexDisplayMode: indexDisplayMode))
            .indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: indexBackgroundDisplayMode))
        }
    }
    
    extension PageView where SelectionValue == Int {
        init(
            indexDisplayMode: PageTabViewStyle.IndexDisplayMode = .automatic,
            indexBackgroundDisplayMode: PageIndexViewStyle.BackgroundDisplayMode = .automatic,
            @ViewBuilder content: @escaping () -> Content
        ) {
            self._selectionInternal = .init(initialValue: 0)
            self._selectionExternal = .constant(0)
            self.indexDisplayMode = indexDisplayMode
            self.indexBackgroundDisplayMode = indexBackgroundDisplayMode
            self.content = content
        }
    }
    

    Now you have a default PageView:

    PageView {
        FirstView()
        SecondView()
        ThirdView()
    }
    

    which can be customised:

    PageView(indexDisplayMode: .always, indexBackgroundDisplayMode: .always) { ... }
    

    or provided with a selection:

    struct ContentView: View {
        @State var selection = 1
    
        var body: some View {
            VStack {
                Text("Selection: \(selection)")
                PageView(selection: $selection, indexBackgroundDisplayMode: .always) {
                    ForEach(0 ..< 3, id: \.self) {
                        Text("Page \($0)")
                            .tag($0)
                    }
                }
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题