Custom native control to show PDF

Back to How-to Discussions

I like to show local Pdf files from the bundle on iOS. The fuse webview is not working in that scenario. I try two approaches following the native controls implementation sample:

  • native iOS UIWebView with bundle path to pdf
  • native iOS Quick Look Framework

The Quick Look Framework needs an UINavigationController or an UIViewController. Both are hostet on a Fuse LeafView. What kind of control is the LeafView on iOS? And is there a handle to it (self) (like the Handle property for the hosted control)? This handle to the LeafView could then be used to attach the QLPreviewController like this:

QLPreviewController *previewController=[[QLPreviewController alloc]init];
previewController.delegate=self;
previewController.dataSource=self;
[self presentModalViewController:previewController animated:YES];
[previewController.navigationItem setRightBarButtonItem:nil];

Another community member has worked on a PDF component recently, so please check this forum thread for details.

Maybe you two could combine efforts, and publish a library on github when you're done?

@prince: Yes, I already studied your code. From my understanding you don't need not to convert your pdf to a file. You can use WebView's data property to show the pdf.

In my case I deliver the pdf files with the app bundle. The file protocol ('file://') is unfortunately not working. I assume security reasons. So I created the UIWebView in a LeafView and try to open the pdf like this

NSString *path = [[NSBundle mainBundle] pathForResource:@"document" ofType:@"pdf"];
NSURL *targetURL = [NSURL fileURLWithPath:path];
NSURLRequest *request = [NSURLRequest requestWithURL:targetURL];
[webView loadRequest:request];

In general the Quick Look Framework would be the more generic one because it can display more document types (iWorks, MS Office, etc.)

@Uldis: Is there a pointer or handle to the Fuse LeafView?

You are telling that the file protocol ('file://') is not working but the code you try to implement use this protocol. if you can print your targetURL you will see that. The process i used to display the pdf with the fuse webview is exactly the same you try to implement right now (exactly the same). Please post the full code , maybe it will be more easy to help like this.

@prince: you're right. In the end it's the same.

If you telling Fuse to iOS bundle a file it's copied to Resources/data with the filename and an id like this file-c2230a48.pdf. There is a file named bundle in this directory which translate the filenames like this:

Custom Control:file.pdf:file-c2230a48.pdf

With this information you can create a valid bundle path like this:

NSURL *targetURL = [[NSBundle mainBundle] URLForResource:@"data/file-c2230a48" withExtension:@"pdf"];

Because the data folder is a referenced folder and the Resources folder is not the path to the pdf file will be like this:

data/

although it's project path is

Custom Control/Resources/data/

At the end this will lead to the absolute path of this:

file:///var/containers/Bundle/Application/D92CF2AA-7585-4B55-868C-6DF6C8F646CB/Custom%20Control.app/data/file-c2230a48.pdf

This is a valid path for the IOS native webview AND the Fuse WebView!

So displaying pdf files is in the end just to know the exact path on the target device.

I will create a path helper that will create the desired bundle path and then use the file protocol...

How to display pdf from bundle:

The project file with the file to bundle:

{
"RootNamespace":"",
"Packages": [
  "Fuse",
  "FuseJS"
],
"Includes": [
  "*",
  "test.pdf:Bundle"
]
}

The app file:

<App >
<JavaScript>
    var BundleHelper = require('BundleHelper');
    var Observable = require('FuseJS/Observable');

    var fileToShow = Observable('about:blank');
    var absolutePath = BundleHelper.getFilePath('test.pdf');

    if (absolutePath) {
        fileToShow.value = 'file://' + absolutePath.replace(' ', '%20');
        console.log(fileToShow.value);
    }

    module.exports = {
        fileToShow: fileToShow
    }
</JavaScript>
<ClientPanel>
    <!-- <Panel>    -->
        <NativeViewHost>
            <WebView Url="{fileToShow}" />
        </NativeViewHost>
    <!-- </Panel> -->
</ClientPanel>

</App>

and the uno helper class:

using Fuse;
using Fuse.Scripting;
using Uno.UX;
using Uno.Compiler.ExportTargetInterop;

[UXGlobalModule]
public class BundleHelper : NativeModule
{

static readonly BundleHelper _instance;

public BundleHelper()
{
    // Make sure we're only initializing the module once
    if (_instance != null) return;

    _instance = this;
    Resource.SetGlobalKey(_instance, "BundleHelper");
    AddMember(new NativeFunction("getBundlePath", (NativeCallback)GetBundlePath));
    AddMember(new NativeFunction("getFilePath", (NativeCallback)GetFilePath));
    AddMember(new NativeFunction("getAppName", (NativeCallback)GetAppName));
}

static string GetFilePath(Context c, object[] args)
{
    if (args.Length > 0) {
        try {
            string bundledFiles = ReadBundledFiles();
            string fileToFind = args[0] as string;
            if (bundledFiles != null) {
                int pos = bundledFiles.IndexOf(fileToFind);
                if (pos != -1) {
                    bundledFiles = bundledFiles.Substring(pos + fileToFind.Length + 1);
                    pos = bundledFiles.IndexOf(':');
                    if (pos != -1) bundledFiles = bundledFiles.Substring(0, pos);
                    return GetBundlePath(c, args) + "/data/" + bundledFiles;
                } else {
                    return null;
                }

                return null;
            }
        } catch(Error e) {
            debug_log(e);
        }
    }


    return null;
}

[Foreign(Language.ObjC)]
public static extern(iOS) string GetBundlePath(Context c, object[] args)
@{
    NSBundle *appBundle = [NSBundle mainBundle];
    NSString *appBundlePath = [appBundle bundlePath];
    return appBundlePath;
@}

public static extern(!iOS) string GetBundlePath(Context c, object[] args)
{
    return null;
}

[Foreign(Language.ObjC)]
public static extern(iOS) string GetAppName(Context c, object[] args)
@{
    NSString *appName = [NSString stringWithUTF8String:getprogname()];
    return appName;
@}

public static extern(!iOS) string GetAppName(Context c, object[] args)
{
    return null;
}

[Foreign(Language.ObjC)]
public static extern(iOS) string ReadBundledFiles()
@{
    NSString *filepath = [[NSBundle mainBundle] pathForResource:@"data/bundle" ofType:@""];
    NSError *error;
    NSString *fileContent = [NSString stringWithContentsOfFile:filepath encoding:NSUTF8StringEncoding error:&error];

    if (error)
        NSLog(@"Error reading file: %@", error.localizedDescription);

    return fileContent;

@}

public static extern(!iOS) string ReadBundledFiles()
{
    return null;
}
  } 

Hi, very interesting discussion. Can anyone explain me: why this don't work when I duplicate webview? Like in code example below:

<ClientPanel>
	<NativeViewHost>
		<Grid RowCount="2" >
			<WebView Url="{fileToShow}" />
			<WebView Url="{fileToShow}" />
		</Grid>
	</NativeViewHost>
</ClientPanel>

I can see white screen and nothing more. But if instead of pdf we try to open web-url in webview, then all work as it should.

Most confusing to me is the fact that if I try show 2 different pdf files - it works!! But it seems impossible to show one file 2 times. Why?

@Emptyfortress: what you're asking is not directly related to the PDF discussion in this thread. Please make a new forum post with all the details, including a complete, ready-to-run reproduction.

Post Stats
  • 8
    replies
  • 1032
    views
  • 4
    users
Frequent Posters