PDF and Image visibility issue

Sreenivasan, Sreejith 415 Reputation points
2025-12-15T11:17:58.5866667+00:00

I am migrating a Xamarin Forms project to MAUI. I have a custom webview for showing the pdf and image files in Xamarin Forms and I migrated it to a handler in MAUI. But the pdf and images are not visible on the UI in MAUI.

Using this thread I have updated the handler and now I can see the download option for pdf but still image is not visible on the UI.

Expected output (For some reason PDF content is not visible):as

Current output is adding below:
Screenshot_1765796266

PdfWebViewer:

public class PdfWebViewer : WebView
{
    public static readonly BindableProperty UriProperty = BindableProperty.Create(propertyName: "Uri", returnType: typeof(string), declaringType: typeof(PdfWebViewer), defaultValue: default(string));

    public string Uri
    {
        get { return (string)GetValue(UriProperty); }
        set { SetValue(UriProperty, value); }
    }

    public event EventHandler UrlLoaded;

    public void OnUrlLoaded()
    {
        UrlLoaded?.Invoke(this, null);
    }
}

PdfWebViewerHandler

public class PdfWebViewerHandler : ViewHandler<PdfWebViewer, global::Android.Webkit.WebView>
{
    private static readonly WebClient _webClient = new WebClient();
    private WebViewClientDelegate _webDelegate;

    public static IPropertyMapper<PdfWebViewer, PdfWebViewerHandler> Mapper =
        new PropertyMapper<PdfWebViewer, PdfWebViewerHandler>(ViewHandler.ViewMapper)
        {
            [nameof(PdfWebViewer.Uri)] = MapUri
        };

    public PdfWebViewerHandler() : base(Mapper) { }

    protected override global::Android.Webkit.WebView CreatePlatformView()
    {
        var webView = new global::Android.Webkit.WebView(Context);

        // Enhanced settings for MAUI compatibility
        webView.Settings.JavaScriptEnabled = true;
        webView.Settings.AllowFileAccess = true;
        webView.Settings.AllowContentAccess = true;
        webView.Settings.AllowUniversalAccessFromFileURLs = true;
        webView.Settings.AllowFileAccessFromFileURLs = true;
        webView.Settings.DomStorageEnabled = true;
        webView.Settings.MixedContentMode = MixedContentHandling.AlwaysAllow;

        return webView;
    }

    protected override void ConnectHandler(global::Android.Webkit.WebView platformView)
    {
        base.ConnectHandler(platformView);

        if (VirtualView != null && platformView != null)
        {
            _webDelegate = new WebViewClientDelegate(VirtualView);
            platformView.SetWebViewClient(_webDelegate);

            if (!string.IsNullOrEmpty(VirtualView.Uri))
            {
                CheckLocalDownload(GetFileNameFromUri());
            }
        }
    }

    private static void MapUri(PdfWebViewerHandler handler, PdfWebViewer view)
    {
        handler?.CheckLocalDownload(handler.GetFileNameFromUri());
    }

    private string GetFileNameFromUri()
    {
        if (VirtualView != null && !string.IsNullOrEmpty(VirtualView.Uri))
        {
            var uri = new Uri(VirtualView.Uri);
            return Path.GetFileName(uri.AbsolutePath);
        }

        return $"{Guid.NewGuid()}.pdf";
    }

    private async void CheckLocalDownload(string fileName)
    {
        try
        {
            var fileDownloaded = await DownloadFile(VirtualView.Uri, fileName);
            if (fileDownloaded)
            {
                await LoadUrl(fileName);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"CheckLocalDownload Exception: {ex}");
        }
    }

    private async Task<bool> DownloadFile(string uri, string filename)
    {
        try
        {
            string documentsPath = Path.Combine(FileSystem.AppDataDirectory, "Documents");

            if (!Directory.Exists(documentsPath))
                Directory.CreateDirectory(documentsPath);

            string localPath = Path.Combine(documentsPath, filename);

            // Check if file already exists
            if (File.Exists(localPath))
            {
                var fileInfo = new FileInfo(localPath);
                if (fileInfo.Length > 0)
                    return true;
            }

            // Use HttpClient instead of WebClient
            using var httpClient = new HttpClient();
            httpClient.Timeout = TimeSpan.FromMinutes(5);

            var response = await httpClient.GetAsync(uri);

            if (response.IsSuccessStatusCode)
            {
                var content = await response.Content.ReadAsByteArrayAsync();
                await File.WriteAllBytesAsync(localPath, content);
                return File.Exists(localPath);
            }

            return false;
        }
        catch (Exception exc)
        {
            System.Diagnostics.Debug.WriteLine($"Download failed: {exc.Message}");
            return false;
        }
    }

    private async Task LoadUrl(string fileName)
    {
        string documentsPath = Path.Combine(FileSystem.AppDataDirectory, "Documents");
        string localPath = Path.Combine(documentsPath, fileName);

        if (!File.Exists(localPath))
        {
            await ShowErrorMessage("File not found", "The requested file could not be located.");
            return;
        }

        string fileType = Path.GetExtension(localPath).Replace(".", string.Empty).ToLower();

        if (PlatformView == null || VirtualView == null)
            return;

        try
        {
            switch (fileType)
            {
                case "pdf":
                    //await LoadPdfFile(localPath, fileName);
                    await LoadPdfWithWebViewer(localPath, fileName);
                    //await LoadPdfAsBase64(localPath);
                    break;

                case "jpg":
                case "jpeg":
                case "png":
                case "gif":
                case "bmp":
                case "webp":
                    await LoadImageFile(localPath);
                    break;

                case "txt":
                    //await LoadTextFile(localPath); // keep existing text loader if you have one
                    break;

                default:
                    await ShowErrorMessage("Unsupported File Type", $"Cannot display .{fileType} files in this viewer.");
                    break;
            }
        }
        catch (Exception ex)
        {
            await ShowErrorMessage("Loading Error", "An error occurred while loading the file.");
        }
    }

    private async Task<bool> CheckAssetExists(string assetPath)
    {
        try
        {
            using var stream = Context.Assets.Open(assetPath);
            return stream != null;
        }
        catch
        {
            return false;
        }
    }

    private async Task LoadPdfFile(string localPath, string fileName)
    {
        // Check if PDF.js assets exist
        bool pdfJsExists = await CheckAssetExists("pdfjs/web/viewer.html");

        if (pdfJsExists)
        {
            try
            {
                // Copy to cache for PDF.js access
                //string cacheDir = Context.CacheDir.AbsolutePath;
                //string cachedPath = Path.Combine(cacheDir, fileName);
                //File.Copy(localPath, cachedPath, true);

                string filesDir = Context.FilesDir.AbsolutePath;
                string pdfDir = Path.Combine(filesDir, "pdfs");
                if (!Directory.Exists(pdfDir))
                    Directory.CreateDirectory(pdfDir);

                string savedPath = Path.Combine(pdfDir, fileName);
                File.Copy(localPath, savedPath, true);

                // Load with PDF.js
                //string pdfUrl = $"file:///android_asset/pdfjs/web/viewer.html?file=../../../cache/{fileName}";
                //PlatformView.LoadUrl(pdfUrl);

                string pdfUrl = $"file:///android_asset/pdfjs/web/viewer.html?file=../../../files/pdfs/{fileName}";
                PlatformView.LoadUrl(pdfUrl);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine($"PDF.js failed: {ex.Message}");
                await ShowPdfError(fileName);
            }
        }
        else
        {
            await ShowPdfError(fileName);
        }
    }

    private async Task LoadPdfWithWebViewer(string localPath, string fileName)
    {
        try
        {
            byte[] pdfBytes = await File.ReadAllBytesAsync(localPath);
            string base64 = Convert.ToBase64String(pdfBytes);

            // Use object/embed instead of iframe
            string html = $@"
    <!DOCTYPE html>
    <html>
    <head>
        <meta name='viewport' content='width=device-width, initial-scale=1.0'>
        <style>
            body {{ margin: 0; padding: 0; }}
            .header {{ background: #2196F3; color: white; padding: 15px; text-align: center; }}
            .pdf-container {{ width: 100%; height: calc(100vh - 60px); }}
        </style>
    </head>
    <body>
        <div class='header'>📄 {fileName}</div>
        <object class='pdf-container'
                data='data:application/pdf;base64,{base64}'
                type='application/pdf'>
            <p>PDF cannot be displayed. <a href='data:application/pdf;base64,{base64}' download='{fileName}'>Download PDF</a></p>
        </object>
    </body>
    </html>";

            PlatformView.LoadDataWithBaseURL(null, html, "text/html", "UTF-8", null);
        }
        catch (Exception ex)
        {
            await ShowErrorMessage("PDF Error", $"Failed to load PDF: {ex.Message}");
        }
    }

    private async Task LoadPdfAsBase64(string pdfPath)
    {
        byte[] pdfBytes = await File.ReadAllBytesAsync(pdfPath);
        string base64 = Convert.ToBase64String(pdfBytes);

        string html = $@"
    <html>
        <body style='margin:0;'>
            <iframe 
                src='data:application/pdf;base64,{base64}' 
                style='width:100%; height:100vh;' 
                frameborder='0'></iframe>
        </body>
    </html>";

        PlatformView.LoadDataWithBaseURL(null, html, "text/html", "UTF-8", null);
    }

    private async Task ShowPdfError(string fileName)
    {
        string html = $@"<html><body style='text-align:center; padding:40px;'>
    <h2>PDF Viewer Unavailable</h2>
    <p>File '{fileName}' downloaded but PDF.js assets are missing.</p>
    </body></html>";
        PlatformView.LoadDataWithBaseURL(null, html, "text/html", "UTF-8", null);
    }

    private async Task LoadImageFile(string imagePath)
    {
        try
        {
            byte[] imageBytes = await File.ReadAllBytesAsync(imagePath);
            string base64 = Convert.ToBase64String(imageBytes);
            string extension = Path.GetExtension(imagePath).ToLower();

            string mimeType = extension switch
            {
                ".jpg" or ".jpeg" => "image/jpeg",
                ".png" => "image/png",
                _ => "image/jpeg"
            };

            string html = $@"<html><body style='margin:0;background:#000;display:flex;justify-content:center;align-items:center;min-height:100vh;'>
        <img src='data:{mimeType};base64,{base64}' style='max-width:100%;max-height:100vh;object-fit:contain;' />
        </body></html>";

            PlatformView.LoadDataWithBaseURL(null, html, "text/html", "UTF-8", null);
        }
        catch (Exception ex)
        {
            await ShowErrorMessage("Image Error", "Failed to load image file.");
        }
    }

    private async Task ShowErrorMessage(string title, string message)
    {
        string html = $@"<html><body style='text-align:center;padding:40px;background:#f5f5f5;'>
    <div style='background:white;padding:30px;border-radius:8px;'>
    <h2 style='color:#e74c3c;'>{title}</h2>
    <p>{message}</p>
    </div></body></html>";

        PlatformView.LoadDataWithBaseURL(null, html, "text/html", "UTF-8", null);
    }




    private class WebViewClientDelegate : WebViewClient
    {
        private readonly PdfWebViewer _pdfWebViewer;

        public WebViewClientDelegate(PdfWebViewer pdfWebViewer)
        {
            _pdfWebViewer = pdfWebViewer;
        }

        public override void OnPageStarted(global::Android.Webkit.WebView view, string url, global::Android.Graphics.Bitmap favicon)
        {
            base.OnPageStarted(view, url, favicon);
        }

        public override void OnPageFinished(global::Android.Webkit.WebView view, string url)
        {
            base.OnPageFinished(view, url);
            _pdfWebViewer?.OnUrlLoaded();
        }
    }
}

Please help me to resolve the issue with both PDF and image.

Developer technologies | .NET | .NET MAUI
{count} votes

Answer accepted by question author
  1. Michael Le (WICLOUD CORPORATION) 6,925 Reputation points Microsoft External Staff Moderator
    2025-12-17T07:11:49.6066667+00:00

    Hello @Sreenivasan, Sreejith ,

    I have attached the updated code for your reference. MAUI.Clinical6/Platforms/Android/Renders/PdfWebViewerHandler.cs.txt

    After testing a few different approaches, these are the changes I made:

    • I switched back to PDF.js with version 3.11.174.
    • For some reason, when using the default viewer.html, the content isn't displayed correctly. So, I implemented a custom HTML viewer using CreateCustomPdfViewer().
    • For images, just a few tweaks to the CSS and HTML layout should fix the issue.
    • I also added a few debug logs to trace the loading process.

    There are still some minor issues with rendering certain elements, but overall, the PDFs and images are displaying much better now. I would still prefer to use the default viewer if possible since a custom implementation means manually adding features like zooming, searching, and more advanced navigation. However, this approach provides a working baseline you can build on.

    Happy holidays.

    1 person found this answer helpful.

0 additional answers

Sort by: Most helpful

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.