theovogg
theovogg

Reputation: 81

PageViewController - white background

I'm trying to apply a .background modifier to my card which is represented by a PageViewController.

The red background is not applied to my card, because the card seems to have a default white background.

He're my file and preview

import SwiftUI

struct PageView<Page:View>: View {

    var viewControllers: [UIHostingController<Page>]

    init(_ views: [Page]) {
        self.viewControllers = views.map { UIHostingController(rootView: $0)}
    }

    var body: some View {


        ZStack {

            Color.black
                .edgesIgnoringSafeArea(.all)

            PageViewController(controllers: viewControllers)
                .background(Color.red)

        }


    }

}

struct PageView_Previews: PreviewProvider {
    static var previews: some View {
        PageView(cards.map { OnboardingCardView(card: $0) })
    }
}

Each card come from a CardView and no background is applied to my card here.

Here's my pageViewController code :

    import SwiftUI

struct PageViewController: UIViewControllerRepresentable {

    var controllers: [UIViewController]

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    func makeUIViewController(context: Context) -> UIPageViewController {

        let pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal)

        pageViewController.dataSource = context.coordinator

        return pageViewController
    }

    func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {

        pageViewController.setViewControllers([controllers[0]], direction: .forward, animated: true)
    }

    class Coordinator: NSObject, UIPageViewControllerDataSource {
        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]

        }

    }

}

What's the problem here ? Thanks a lot.

Upvotes: 0

Views: 630

Answers (2)

Aspid
Aspid

Reputation: 699

Background is set in your UIHostingController for every exact card by default. You can set background color to .clear manually to make SwiftUI background makes effect:

struct PageView<Page:View>: View {

    var viewControllers: [UIHostingController<Page>]

    init(_ views: [Page]) {
        self.viewControllers = views.map { UIHostingController(rootView: $0)}
        for viewController in viewControllers{
            viewController.view.backgroundColor = .clear
        }

    }
...
}

Upvotes: 1

Aspid
Aspid

Reputation: 699

Check my full SwiftUI realisation of UIPageViewController. It doesn't use UIKit, so it would be easier to control.

import SwiftUI
extension OnboardingCard{
    static var test: [OnboardingCard]{
        [
        OnboardingCard(image: "onboarding", title: "Card 1", description: "Card 1 description."),
        OnboardingCard(image: "onboarding", title: "Card 2", description: "Card 2 description."),
        OnboardingCard(image: "onboarding", title: "Card 3", description: "Card 4 description.")
        ]
    }
}

struct ContentView: View {
    var directionForward = true
    @State var currentCard: OnboardingCard
    var cards: [OnboardingCard]
    @State var xOffset: CGFloat = 0
    init(cards newCards: [OnboardingCard]){
        self.cards = newCards
        guard let startCard = newCards.first else {fatalError("no cards")}
        self._currentCard = State(initialValue: startCard)
    }
    var body: some View {
        GeometryReader{geometry in
            HStack{
                ForEach(self.cardsSorted()){card in
                    OnboardingCardView(card: card)
                        .frame(width: geometry.size.width)
                }
            }
            .offset(x: self.xOffset, y: 0)
            .gesture(DragGesture()
            .onChanged(){value in
                self.xOffset = value.location.x - value.startLocation.x
                }
            .onEnded(){value in
                let inPercent = 100 * self.xOffset / (geometry.size.width / 2)
                let changedCount = inPercent / 100
                if changedCount < -1{//drag left to go to next one
                    guard let currentCardInd = self.cards.firstIndex(where: {$0.id == self.currentCard.id}) else{fatalError("cant find current card")}
                    let nextInd = self.cards.index(currentCardInd, offsetBy: Int(-changedCount))
                    if self.cards.indices.contains(nextInd){
                        self.currentCard = self.cards[nextInd]
                        self.xOffset = geometry.size.width + self.xOffset
                    }else{
                        guard let firstCard = self.cards.first else{fatalError("cant get first card")}
                        self.currentCard = firstCard
                        self.xOffset = geometry.size.width + self.xOffset
                    }
                }else if changedCount > 1{//drag right to go to previos one
                    guard let currentCardInd = self.cards.firstIndex(where: {$0.id == self.currentCard.id}) else{fatalError("cant find current card")}
                    let previosInd = self.cards.index(currentCardInd, offsetBy: Int(-changedCount))
                    if self.cards.indices.contains(previosInd){
                        self.currentCard = self.cards[previosInd]
                        self.xOffset = -geometry.size.width + self.xOffset
                    }else{
                        guard let lastCard = self.cards.last else{fatalError("cant get last card")}
                        self.currentCard = lastCard
                        self.xOffset = -geometry.size.width + self.xOffset
                    }
                }

                withAnimation(.easeInOut(duration: 0.5)){
                    self.xOffset = 0
                }
            })
        }
    }
    func cardsSorted() -> [OnboardingCard]{
        guard let currentCardInd = self.cards.firstIndex(where: {$0.id == self.currentCard.id}) else{fatalError("cant find current card")}
        guard let firstInd = self.cards.firstIndex(where: {$0.id == cards.first?.id}) else{fatalError("cant find current card")}
        guard let lastInd = self.cards.firstIndex(where: {$0.id == cards.last?.id}) else{fatalError("cant find current card")}
        var nextInd = self.cards.index(after: currentCardInd)
        if self.cards.indices.contains(nextInd) == false{
            nextInd = firstInd
        }
        var previosInd = self.cards.index(before: currentCardInd)
        if self.cards.indices.contains(previosInd) == false{
            previosInd = lastInd
        }
        return [self.cards[previosInd], self.cards[currentCardInd],self.cards[nextInd]]
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(cards: OnboardingCard.test)
    }
}

it have less code, and it have transparent logic. You can modify checks to adjust switch sensitivity if you want (by editing compare if changedCount > 1). You can use gesture end point predictions to calculate and adjust animation speed or even to switch more than one card at once. It's fully up to you. Or you can just use if as is.

Upvotes: 0

Related Questions