how to get UIViewController associated with a Page?

GeorgeCookGeorgeCook PEUniversity ✭✭✭

or the uiview associated with a stack layout?
I'm creating a generic page-Container control to allow embedding of pages - to do this properly on iOS I need a reference to the most logical parent ViewCotnroller, which in xamarin forms is the pages view controller.

I know how to get a reference to the viewcontroller via CreateViewController; but this will not work for when it's the parent I want to access.

Any ideas how to do this?
Would it work on other platforms too?

Answers

  • adamkempadamkemp USInsider, Developer Group Leader mod

    The answer is the same as this answer. What you need to do is basically this:

    var renderer = Platform.GetRenderer (page);
    if (renderer == null)
    {
        renderer = RendererFactory.GetRenderer (page);
        Platform.SetRenderer (page, renderer);
    }
    var viewController = renderer.ViewController;
    

    But the Platform type is internal, which means you have to use reflection to do this.

  • GeorgeCookGeorgeCook PEUniversity ✭✭✭
    edited May 2015

    Ok. I saw the other answer plus it's introspection workaround. I thought that was just for creating renderers. I thought of another workaround. I Could subclass a page and expose the vc to a static var in the renderer. Not clean but safer than relying on internal bars and introspection and fitsy use case
    I'm looking for a virwcontroller hook to allow me to repurpose my view controller container class, which I end up using on most my iOS projects. I see it being particularly powerful in xamarin as I can then write quite complex layout containers for pages in cross platform code.

    Edited once I got out the Cinema queue. iPhone did a job on my spelling. ;)

  • GeorgeCookGeorgeCook PEUniversity ✭✭✭
    edited May 2015

    @adamkemp thought you might like to know I had great joy with this approach (of creating a page renderer which allows one to lookup underlying os level viewcontrollers for a given xamarin view)!!

    This is made with the following code:

    xaml

        <controls:ViewHookingPage xmlns="http://xam etc etc>
            <StackLayout>
                <controls:CustomTabPageHeader />
                <ScrollView x:Name="ChildrenScrollView" Orientation="Horizontal">
                <StackLayout x:Name="InnerContent" Orientation="Horizontal"/>
                </ScrollView>
            </StackLayout>
        </controls:ViewHookingPage>
    

    code behind:

        var eventsPage = (EventsPage)ViewFactory.CreatePage<EventsPageVM> ();
                _infoPage = (InfoPage)ViewFactory.CreatePageWithExistingViewModel (ViewModel.InfoPageVM);
                _sessionsPage = (SessionsPage)ViewFactory.CreatePageWithExistingViewModel (ViewModel.SessionPageVM);
                _mediaPage = (MediaPage)ViewFactory.CreatePageWithExistingViewModel (ViewModel.MediaPageVM);
                _isChildrenCreated = true;
    
                var pages = new Page[]{ eventsPage, _infoPage, _sessionsPage, _mediaPage };
                foreach (var page in pages) {
                    var contentView = new PageViewContainer () {
                        Content = page,
                        WidthRequest = 320,
                        HeightRequest = 300,
                        BackgroundColor = Color.Red,
                    };
                    InnerContent.Children.Add (contentView);
                }
    

    I've not written the carousel code yet (but now I'm unblocked I shall). This is great news as it means I can now write any view navigation strategy I see fit, without having to duplicate any of my view logic in the cross platform renderers.

    It's very simple, the ViewHookingPage has no trouble getting the correct view controller reference, which I expose through a static variable (currently just RootViewController; but it'd be trivial to make a weak lookup based of XamarinPage/native ViewController, should one need more layers of granularity).

    The Viewcontroller container is just my bog-standard view I've had since iOS 5, ported to c#, with one or two extra lines to manage the Xamarin View layout cycle.

    I see a lot of mileage in this approach as xamarin forms animations are native performance (in my experience), so I see no need to re-write all my custom ui/navigation/transition code (which let's face it, can end up with all kinds of perfectionist edge cases) on many platforms.

    So the point is : would you think this approach will work on other platforms? I really hope so, as this will blow the doors open for me to be very creative with Xamarin Forms apps.

    Here's the code for the view container.

        [assembly: ExportRenderer (typeof(PageViewContainer), typeof(PageViewContainerRenderer))]
        namespace TwinTechs.Ios.Controls
        {
            public class PageViewContainerRenderer : ViewRenderer<PageViewContainer,UIView>
            {
                public PageViewContainerRenderer ()
                {
                }
    
                ViewControllerContainer _viewControllerContainer;
    
                protected override void OnElementChanged (ElementChangedEventArgs<PageViewContainer> e)
                {
                    base.OnElementChanged (e);
                    var pageViewContainer = e.NewElement as PageViewContainer;
    
                    if (e.NewElement != null) {
                        _viewControllerContainer = new ViewControllerContainer (Bounds);
                        SetNativeControl (_viewControllerContainer);
                    }
                }
    
                void ChangePage (Page page)
                {
                    if (page != null) {
                        try {
                            var viewController = page.CreateViewController ();
                            _viewControllerContainer.ParentViewController = ViewHookingPageRenderer.RootViewController;
                            _viewControllerContainer.ViewController = viewController;
                        } catch (Exception ex) {
                            Console.WriteLine ("error creating page " + ex.Message);
                        }
                    } else {
                        _viewControllerContainer = null;
                    }
                }
    
                public override void LayoutSubviews ()
                {
                    base.LayoutSubviews ();
                    var page = Element?.Content;
                    if (page != null) {
                        page.Layout (new Rectangle (0, 0, Bounds.Width, Bounds.Height));
                    }
                    _viewControllerContainer.Frame = Bounds;
                }
    
                protected override void OnElementPropertyChanged (object sender, System.ComponentModel.PropertyChangedEventArgs e)
                {
                    base.OnElementPropertyChanged (sender, e);
                    if (e.PropertyName == "Content" || e.PropertyName == "Renderer") {
                        Device.BeginInvokeOnMainThread (() => ChangePage (Element?.Content));
                    }
                }
    
            }
        }
    
  • @adamkemp said:
    The answer is the same as this answer. What you need to do is basically this:

    var renderer = Platform.GetRenderer (page);
    if (renderer == null)
    {
        renderer = RendererFactory.GetRenderer (page);
        Platform.SetRenderer (page, renderer);
    }
    var viewController = renderer.ViewController;
    

    But the Platform type is internal, which means you have to use reflection to do this.

    Thanks... it works for me.

Sign In or Register to comment.