iOS

Implement page view in SwiftUI

Page View is common feature in any mobile application. There are some methods that help implementing page view in SwiftUI, but they are not useful enough to deal with real project. As a result, in this post I’ll talk about these methods’ limits and introduce a solution for implementing page view in a SwiftUI project.

Contents

The requirement

When implementing pager view I usually need the solution satisfy the following requirements:

  • Support loop.
  • Allow customize item size.
  • Can customize UIPageControl.

Currently available solutions

To solve this problem I have search on the internet there are 3 possible solutions to implement page view in SwiftUI:

  1. Using tabViewStyle()
  2. Interfacing UIPageViewController
  3. Using library SwiftUIPager

Let’s take a look at how these methods work and check their pros and cons.

Using tabViewStyle() to implement page view in SwiftUI

The first method to implement page view in SwiftUI uses TabView with modifier tabViewStyle() :

struct ContentView: View {
    var body: some View {
        TabView {
            Text("First")
            Text("Second")
            Text("Third")
            Text("Fourth")
        }
        .tabViewStyle(.page)
    }
}

This solution is simple. But it does not satisfy any of my requirements including loop, custom size or custom UIPageControl.

Interfacing UIPageViewControllerto implement page view in SwiftUI

The second method to implement page view in SwiftUI use UIPageController inside SwiftUI wrapper:

struct PageViewController<Page: View>: UIViewControllerRepresentable {
    var pages: [Page]

    func makeUIViewController(context: Context) -> UIPageViewController {
        let pageViewController = UIPageViewController(
            transitionStyle: .scroll,
            navigationOrientation: .horizontal)

        return pageViewController
    }

    func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
        pageViewController.setViewControllers(
            [UIHostingController(rootView: pages[0])], direction: .forward, animated: true)
    }
}

This solution is also the same as the first, it does not satisfy any requirements. So it is out of the table.

Using library SwiftUIPager to implement page view in SwiftUI

SwiftUIPager is a library for implementing pager view in SwiftUI using SwiftUI view. It provides many features and satisfy all the requirements. Unfortunately there is a bug when using loop pager view:

Video demonstrate bug when scroll looping pager view of library SwiftUIPager

As you can see at the end of the video the pager view’s content disappear and cannot scroll any. It’s been around more than 3 months since I found out this bug and nothing has been done to fix this. So we cannot use this solution too.

My solution to implement page view in SwiftUI

As described above, there is no solution that satisfy all requirements without any bugs. Because of that I decided to use a UIKit library and integrate it into SwiftUI: FSPagerView to implement page view in SwiftUI. Why I choose this library:

  • It satisfy all my requirements.
  • FSPagerView work stably without any bugs.
  • It supports customize UIPageControl.
  • Simple API.

After sometimes integrate FSPagerView into SwiftUI, I have created a repo, you can check try it for yourself. Here is sample code for using FSPagerView in SwiftUI from the repo:

struct ContentView: View {
    private struct TutorialItem: Identifiable {
        let id: UUID = UUID()
        let image: String
    }

    private let images: [TutorialItem] = [
        TutorialItem(image: "1"),
        TutorialItem(image: "2"),
        TutorialItem(image: "3"),
        TutorialItem(image: "4"),
        TutorialItem(image: "5"),
        TutorialItem(image: "6"),
        TutorialItem(image: "7"),
    ]
    
    @State var currentPage: Int = 0
    var body: some View {
        ZStack(content: {
            FSPagerViewSUI($currentPage, images) { item in
                Image(item.image)
                    .resizable()
                    .frame(
                        maxWidth: .infinity,
                        maxHeight: .infinity
                    )
            }

            VStack(alignment: .center, spacing: nil, content: {
                FSPageControlSUI(currentPage: $currentPage)
                    .numberOfPages(images.count)
                    .contentHorizontalAlignment(.right)
                    .contentInsets(UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20))                    .frame(maxWidth: .infinity)
                    .frame(height: 20)
                .background(Color.black.opacity(0.5))
            })
            .frame(
                maxWidth: .infinity,
                maxHeight: .infinity,
                alignment: .bottomTrailing
            )
        })
        .aspectRatio(375.0/193.0, contentMode: .fit)
    }
}

Wraps Up

So in this post I have explained my experiences and solution when implementing pager view in SwiftUI. This may change in the future and I’ll update this post if changes are available.

Leave a Reply

Your email address will not be published. Required fields are marked *