John Cogan
John Cogan

Reputation: 1060

Getting the TRUE current page from a UIPageViewController and/or its DataSource

I have created a tutorial window in storyboard with two views, one to hold show the tutorial the other used as a template for each page of content.

Some elements are coded programmatically on the ViewDidLoad event.

The PageViewController is working 100% as required, it shows the three pages of content and allows swiping backwards and forwards without issue.

I've added a UIPageControl programmatically to the main ViewController but for the life of me cannot update its CurrentPage value correctly. Accessing the datasources PageIndex value gives me odd results when swiping back and forth.

Is there a reliable way to know exactly which page is been displayed ?

Or to know which direction the page transition moved, this way I can manually update? Not entirely sure how UIPageViewControllerNavigationDirection is accessed from the pagecontrollers 'DidFinishAnimating' event.

My main view controller code is as follows:

using Foundation;
using System;
using UIKit;

namespace Performance
{
    partial class VCOnboardHome : UIViewController
    {
        const int pageCount = 3;

        public UIPageViewController pvcOnboarding{ get; set; }

        private OnboardingDataSource onboardDataSource;

        UIStoryboard board;

        public UIPageControl pgControlIndicator;

        public VCOnboardHome (IntPtr handle) : base (handle)
        {

        }

        /// <summary>
        /// ViewDidLoad event method
        /// </summary>
        public override void ViewDidLoad ()
        {
            base.ViewDidLoad ();

            board = UIStoryboard.FromName ("Main", null);

            // Programmatically create a PageView controller
            pvcOnboarding = new UIPageViewController (UIPageViewControllerTransitionStyle.Scroll, 
                UIPageViewControllerNavigationOrientation.Horizontal,
                UIPageViewControllerSpineLocation.None, 20f);

            // PageView Controller datasource
            var views = CreateViews ();
            onboardDataSource = new OnboardingDataSource (views);
            pvcOnboarding.DataSource = onboardDataSource;
            pvcOnboarding.SetViewControllers (new UIViewController[] { views [0] },
                UIPageViewControllerNavigationDirection.Forward,
                false, null);

            // Set PageView size
            pvcOnboarding.View.Frame = View.Bounds;

            // Add the page view control to this view controller
            Add (pvcOnboarding.View);

            // Create Page Control
            var frame = UIScreen.MainScreen.Bounds;
            pgControlIndicator = new UIPageControl ();
            pgControlIndicator.Frame = new CoreGraphics.CGRect (20f, frame.Height - 60f, frame.Width - 40f, 40f);
            pgControlIndicator.Pages = pageCount;
            pgControlIndicator.UserInteractionEnabled = false;

            Add (pgControlIndicator);

            // Update the Page Control to indicate the current page we are showing.
            // Only do this if the full page transition happened and not a partial page turn
            pvcOnboarding.DidFinishAnimating += (sender, e) => {

                foreach(UIViewController u in e.PreviousViewControllers){
                    // TODO - Not needed, remove once page control working
                    // u = the previous viewcontroller
                }

                if(e.Finished && e.Completed){
                    // Page transition completed
                    // Update Page Control here
                }else{
                    // Incomplete page transition
                }
            };


        }

        // Content for each page
        VCOnboardContentNew[] CreateViews ()
        {
            var pageData = new [] {
                new ContentOnBoardData {
                    headerLblText = @"Page 1",
                    bodyContentText = @"Page 1 body text blah blah blah blah",
                    buttonText = @"Ok, next no. 1",
                    pageImage = UIImage.FromBundle("ios_images_v2/onboarding/icon-qr-code.png"),
                    currentPage = 1,
                    totalPages = 3
                },
                new ContentOnBoardData {
                    headerLblText = @"Page 2",
                    bodyContentText = @"Page 2 body text blah blah blah blah",
                    buttonText = @"Ok, next no. 2",
                    pageImage = UIImage.FromBundle("ios_images_v2/onboarding/icon-id-check.png"),
                    currentPage = 2,
                    totalPages = 3
                },
                new ContentOnBoardData {
                    headerLblText = @"Page 3",
                    bodyContentText = @"Page 3 body text blah blah blah blah",
                    buttonText = @"Ok, got it",
                    pageImage = null,
                    currentPage = 3,
                    totalPages = 3
                }
            };

            var views = new VCOnboardContentNew[pageData.Length]; 
            for (int i = 0; i < pageCount; i++) {
                int pageIndex = i;

                views [i] = (VCOnboardContentNew)board.InstantiateViewController ("sbid_onboardcontent");
                views [i].PageIndex = pageIndex;
                views [i].HeaderText = pageData [i].headerLblText;
                views [i].ContentText = pageData [i].bodyContentText;
                views [i].PageImage = pageData [i].pageImage;
                views [i].CurrentPage = pageData [i].currentPage;
                views [i].TotalPages = pageCount;
                views [i].ButtonText = pageData [i].buttonText;

                views [i].ButtonClicked += (s, e) => {
                    DismissViewController (true, null);
                };
            }

            return views;
        }
    }

    /// <summary>
    /// Onboarding data source.
    /// </summary>
    class OnboardingDataSource : UIPageViewControllerDataSource
    {
        readonly VCOnboardContentNew[] _views;

        public OnboardingDataSource (VCOnboardContentNew[] views)
        {
            _views = views;
        }

        /// <summary>
        /// Gets the previous view controller.
        /// </summary>
        /// <returns>The previous view controller.</returns>
        /// <param name="pageViewController">Page view controller.</param>
        /// <param name="referenceViewController">Reference view controller.</param>
        public override UIViewController GetPreviousViewController (UIPageViewController pageViewController, UIViewController referenceViewController)
        {
            int index = ((VCOnboardContentNew)referenceViewController).PageIndex;

            bool controlCheck = (index <= 0);
            UIViewController vcToReturn = controlCheck ? null : (_views [index - 1]);

            return vcToReturn;
        }

        /// <summary>
        /// Gets the next view controller.
        /// </summary>
        /// <returns>The next view controller.</returns>
        /// <param name="pageViewController">Page view controller.</param>
        /// <param name="referenceViewController">Reference view controller.</param>
        public override UIViewController GetNextViewController (UIPageViewController pageViewController, UIViewController referenceViewController)
        {
            int index = ((VCOnboardContentNew)referenceViewController).PageIndex;

            bool controlCheck = index + 1 >= _views.Length;
            UIViewController vcToReturn = controlCheck ? null : _views [index + 1];

            return vcToReturn;
        }
    }

    /// <summary>
    /// Content onboard data.
    /// </summary>
    struct ContentOnBoardData
    {
        public string headerLblText;
        public string bodyContentText;
        public string buttonText;
        public UIImage pageImage;
        public int currentPage;
        public int totalPages;
    }
}

My Page content code view controller is as follows:

using System;
using UIKit;

namespace Performance
{
    /// <summary>
    /// Class: VCOnboardContentNew
    /// </summary>
    partial class VCOnboardContentNew : UIViewController
    {
        public EventHandler ButtonClicked;

        public int PageIndex { get; set; }

        public string HeaderText{ get; set; }

        public UIImage PageImage{ get; set; }

        public int CurrentPage{ get; set; }

        public int TotalPages{ get; set; }

        public string ContentText{ get; set; }

        public string ButtonText{ get; set; }

        public VCOnboardContentNew (IntPtr handle) : base (handle)
        {

        }

        public override void ViewDidLoad ()
        {
            base.ViewDidLoad ();

            // Set text for the main title
            lblTest.Font = UIFont.FromName("FreightDispLight", 26f);
            lblTest.Text = HeaderText;

            // Set text for body content
            lblContentBodyText.Font = UIFont.FromName("FreightDispLight", 14f);
            lblContentBodyText.Text = ContentText;

            pgCtrlWhichPageWeOn.Hidden = true;

            if(PageImage != null){
                pageContentImage.Hidden = false;
                pageContentImage.Image = PageImage;
            }else{
                pageContentImage.Hidden = true;
            }

            btnCallToAction.SetTitle (ButtonText, UIControlState.Normal);
            btnCallToAction.TouchUpInside += (object sender, EventArgs e) => {
                if (ButtonClicked != null) {
                    ButtonClicked.Invoke (this, null);
                }
            };
        }
    }
}

Upvotes: 1

Views: 629

Answers (2)

Somaar
Somaar

Reputation: 155

Old question but was recently struggling with the same issue. Finally found a nice solution.

Provide your UIPageViewController with:

public static int pageIndex = 0; // or whatever start index

Then for each of your UIViewControllers to be loaded override ViewDidAppear:

public override void ViewDidAppear(bool animated)
    {
        base.ViewDidAppear(animated);
        MyCystomPageViewController.pageIndex = 1; // the index of this viewcontroller
    }

This works since ViewDidAppear gets called when the view is added as subview. And thus will be called everytime you swipe.

Upvotes: 0

pbasdf
pbasdf

Reputation: 21536

When the transition style is set to scroll, the page view controller seems to:

  1. As soon as one transition finishes, it immediately calls for the next (or previous) View Controller (with GetNextViewController), even though the user might not have started the next swipe; and
  2. It keeps the view controller that was transitioned from, and assumes that it is the previous VC, so it doesn't call for the previous VC (with GetPreviousViewController) if the user swipes back.

This makes it pretty difficult to keep track of which VC is actually currently showing pretty difficult. If found (see this answer) that I had to use both the WillTransition event and the DidFinishAnimating event. I'm not familiar with Xamarin and C#, so forgive me if this syntax is way off, but I think something like this:

        pvcOnboarding.WillTransition += (sender, e) => {

            nextVCIndex = ((VCOnboardContentNew)e.PendingViewControllers[0]).PageIndex
        }

        pvcOnboarding.DidFinishAnimating += (sender, e) => {

            if(e.Finished && e.Completed){
                // Page transition completed
                currentVCIndex = nextVCIndex
                // Update Page Control here

            }else{
                // Incomplete page transition
            }
        };

You'll need to add currentVCIndex and nextVCIndex as class level variables.

Upvotes: 1

Related Questions