0
votes

After updating the device to iOS 10, QLPreviewController stopped to display correctly the documents. It shows the white screen. I have extracted the sample scenario from the app.

It contains single page with two buttons that should load two different documents:

<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        xmlns:local="clr-namespace:QuickLookIOS10Test"
        x:Class="QuickLookIOS10Test.QuickLookIOS10TestPage">
    <StackLayout Orientation="Vertical">
        <Button Text="Load first doc" Clicked="OnLoadFirstClicked"/>
        <Button Text="Load second doc" Clicked="OnLoadSecondClicked"/>
        <Button Text="Navigate forward" Clicked="OnForwardClicked"/>

        <local:QLDocumentView
            x:Name="DocumentView"
            BackgroundColor="Silver"
            HorizontalOptions="FillAndExpand"
            VerticalOptions="FillAndExpand"/>
    </StackLayout>
</ContentPage>

where:

public class QLDocumentView : View
{
    public static readonly BindableProperty FilePathProperty =
        BindableProperty.Create(nameof(FilePath), typeof(string), typeof(QLDocumentView), null);

    public string FilePath
    {
        get { return (string)GetValue(FilePathProperty); }
        set { SetValue(FilePathProperty, value); }
    }
}

There is a custom renderer involved:

public class QLDocumentViewRenderer : ViewRenderer<QLDocumentView, UIView>
{
    private QLPreviewController controller;

    public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
    {
        //This is a fix to prevent incorrect scaling after rotating from portrait to landscape.
        //No idea why does this work :( Bug #101639
        return new SizeRequest(Size.Zero, Size.Zero);
    }

    protected override void OnElementChanged(ElementChangedEventArgs<QLDocumentView> e)
    {
        base.OnElementChanged(e);

        if (Control == null)
        {
            controller = new QLPreviewController();
            SetNativeControl(controller.View);
        }

        RefreshView();
    }

    protected override void OnElementPropertyChanged(object sender,
                                                     System.ComponentModel.PropertyChangedEventArgs e)
    {
        base.OnElementPropertyChanged(sender, e);

        if (e.PropertyName == QLDocumentView.FilePathProperty.PropertyName)
        {
            RefreshView();
        }
    }

    private void RefreshView()
    {
        DisposeDataSource();

        if (Element?.FilePath != null)
        {
            controller.DataSource = new DocumentQLPreviewControllerDataSource(Element.FilePath);
        }

        controller.ReloadData();
    }

    protected override void Dispose(bool disposing)
    {
        base.Dispose(disposing);

        if (disposing)
        {
            DisposeDataSource();
            DisposeController();
        }
    }

    private void DisposeDataSource()
    {
        var dataSource = controller.DataSource;
        controller.DataSource = null;
        dataSource?.Dispose();
    }

    private void DisposeController()
    {
        controller?.Dispose();
        controller = null;
    }

    private class DocumentQLPreviewControllerDataSource : QLPreviewControllerDataSource
    {
        private readonly string fileName;

        public DocumentQLPreviewControllerDataSource(string fileName)
        {
            this.fileName = fileName;
        }

        public override nint PreviewItemCount(QLPreviewController controller)
        {
            return 1;
        }

        public override IQLPreviewItem GetPreviewItem(QLPreviewController controller, nint index)
        {
            NSUrl url = NSUrl.FromFilename(fileName);
            return new QlItem(url);
        }

        private sealed class QlItem : QLPreviewItem
        {
            private readonly NSUrl itemUrl;

            public QlItem(NSUrl uri)
            {
                itemUrl = uri;
            }

            public override string ItemTitle { get { return string.Empty; } }

            public override NSUrl ItemUrl { get { return itemUrl; } }

            protected override void Dispose(bool disposing)
            {
                base.Dispose(disposing);

                if (disposing)
                {
                    this.itemUrl?.Dispose();
                }
            }
        }
    }
}

If the application setups the main page like below:

MainPage = new NavigationPage(new QuickLookIOS10TestPage());

it does work on iOS 9.3 but not on iOS 10. If I remove NavigationPage:

MainPage = new QuickLookIOS10TestPage();

it works on both iOS versions.

The code behind for button clicks just sets the FilePath property of the control.

Sample app demonstrating the problem

Xamarin Forms 2.3.2.127

Xamarin Studio 6.1.1 (build 15)

1
If you think this used to work on iOS 9 and now doesn't anymore on iOS 10, you should probably let them know on bugreport.apple.com!Raffael
I still don't know if it is iOS, Xamarin or my renderer code issue. I cannot reproduce it using Swift and XCodeaguyngueran

1 Answers

2
votes

I've faced with the same problem. It looks like something was changed or even broken in QuickLook in iOS10, but the solution is quite simple:

public class PdfViewerControlRenderer : ViewRenderer<PdfViewerControl, UIView>
{
    private readonly bool IsOniOS10;

    private UIViewController _controller;
    private QLPreviewController _qlPreviewController;

    public PdfViewerControlRenderer()
    {
        IsOniOS10 = UIDevice.CurrentDevice.CheckSystemVersion(10, 0);
    }

    protected override void OnElementChanged(ElementChangedEventArgs<PdfViewerControl> e)
    {
        if (e.NewElement != null)
        {
            _controller = new UIViewController();
            _qlPreviewController = new QLPreviewController();

            //...
            // Set QuickLook datasource here
            //...

            if (!IsOniOS10)
            {
                _controller.AddChildViewController(_qlPreviewController);
                _controller.View.AddSubview(_qlPreviewController.View);
                _qlPreviewController.DidMoveToParentViewController(_controller);
            }
            SetNativeControl(_controller.View);
        }
    }

    public override void LayoutSubviews()
    {
        base.LayoutSubviews();
        _controller.View.Frame = Bounds;
        _controller.View.AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;
        _qlPreviewController.View.Frame = Bounds;
        if (IsOniOS10)
        {
            _controller.View.AddSubview(_qlPreviewController.View);
            _qlPreviewController.DidMoveToParentViewController(_controller);
        }
    }
}

Result: QuickLook on iOS10