0
votes

I follow Apple's example interfacing-with-uikit to use PageView in SwiftUI. And I want to implement it in my app. The scenario is that in MovieView, when the user taps an image of image list then it triggers call to PageView with views and selected index as parameters.

But when I click on any item of image list, it always show the page view with the 1st element. I try to change it in init() of PageView, however it is not working.

In console, I try to print something, and I see the currentPage is not changed.

ReceivedIdx: 0
Current page: 1
ReceivedIdx: 3
Current page: 1
ReceivedIdx: 5
Current page: 1

Currently, currentPage is connected between PageView & PageViewCollection, but how to connect selectedIdx with them? Because it is the trigger point for which image is selected.

MovieView

...
private struct ImageList: View {
    var images: [ImageViewModel]
    @State private var showSheet = false
    @State private var selectedIdx = 0

    var body: some View {

        VStack(alignment: .leading) {
            Text("Images")
                .font(.headline)
            ScrollView(.horizontal) {
                HStack(alignment: .top, spacing: 6) {
                    ForEach(0..<images.count, id: \.self) { i in
                        KFImage(source: .network(self.images[i].fileURL))
                            .resizable()
                            .frame(width: 200)
                            .aspectRatio(1.77, contentMode: .fit)
                            .onTapGesture {
                                self.selectedIdx = i
                                self.showSheet.toggle()
                        }
                    }
                }.sheet(isPresented: $showSheet) {
                    PageView(self.images.map { PresentedImageView(image: $0) }, selectedIdx: self.selectedIdx)
                }
            }.frame(height: 120)
        }
        .padding(.horizontal).padding(.bottom)
    }
}

private struct PresentedImageView: View {
    var image: ImageViewModel

    var body: some View {
        KFImage(source: .network(image.fileURL))
        .resizable()
        //.frame(width: gr.size.width - 6, alignment: .center)
        .aspectRatio(1.77, contentMode: .fit)
    }
}
...

PageView

import SwiftUI

struct PageView<Page: View>: View {

    var viewControllers: [UIHostingController<Page>]
    var selectedIdx = 0
    @State var currentPage = 1

    init(_ views: [Page], selectedIdx: Int) {
        self.viewControllers = views.map { UIHostingController(rootView: $0) }
        self.currentPage = selectedIdx
        print("ReceivedIdx: \(selectedIdx)")
        print("Current page: \(currentPage)")
    }

    var body: some View {
        PageViewController(controllers: viewControllers, currentPage: $currentPage)
    }
}

//struct PageView_Previews: PreviewProvider {
//    static var previews: some View {
//        PageView(features.map { FeatureCard(landmark: $0) })
//            .aspectRatio(3/2, contentMode: .fit)
//    }
//}

PageViewController

import SwiftUI
import UIKit

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) {
        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
            }
        }
    }
}
1

1 Answers

0
votes

Finally, I found the answer From this post in stackoverflow. this morning!

In one word:

SwiftUI doesn't allow you to change @State in the initializer but you can initialize it.

So in my case, remove the initial value on @State currentPage and set it directly using State(initialValue:) in your init().

Remember to use _ before currentPage.

import SwiftUI

struct PageView<Page: View>: View {

    var viewControllers: [UIHostingController<Page>]
    @State var currentPage: Int

    init(_ views: [Page], selectedIdx: Int) {
        self.viewControllers = views.map { UIHostingController(rootView: $0) }
        _currentPage = State(initialValue: selectedIdx)
    }

    var body: some View {
        PageViewController(controllers: viewControllers, currentPage: $currentPage)
    }
}