Objective-C class retain and utilize in Uno

Hello, I’m trying to extend the capabilities of a PrintManager module to be able to print an html file on iOS. I have created a separate class called WebViewPrinter which creates a UIWebView (without displaying it), loads a URL (and waits for it to complete loading), and then initiates the process to print it UIPrintInteractionController.

Currently, I’m trying to figure out how to utilize the Objective-C class WebViewPrinter from PrintManager (the Uno class). I believe Automatic Reference Counting is destroying the WebViewPrinter before it can do its work, so I need to be able to initialize it and retain it properly and perhaps even later destroy it by releasing the reference to it?? Any help in how to complete this?

WebViewPrinter.h

#import <UIKit/UIKit.h>

@interface WebViewPrinter : NSObject <UIWebViewDelegate>

- (void)printWebPage: (NSString *)pathStr

@end

WebViewPrinter.m

#import "WebViewPrinter.h"


@interface WebViewPrinter ()

@property (strong) UIWebView *myWebView;
@property (strong) NSString* jobName;
@end



@implementation WebViewPrinter

#pragma mark - UIWebViewDelegate

- (void)webViewDidStartLoad:(UIWebView *)webView
{
}


- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    NSURL *url = [webView.request URL];
    [self doPrint];
}


- (void) doPrint
{

    UIPrintInteractionController *controller = [UIPrintInteractionController sharedPrintController];
    if(!controller){
        NSLog(@"Couldn't get shared UIPrintInteractionController!");
        return;
    }

    UIPrintInteractionCompletionHandler completionHandler =
    ^(UIPrintInteractionController *printController, BOOL completed, NSError *error) {
        if(!completed && error){
            NSLog(@"FAILED! due to error in domain %@ with error code %u", error.domain, (unsigned int)error.code);
        }

        if(self.myWebView != nil) {
            self.myWebView.delegate = nil;
            self.myWebView = nil;
            NSLog(@"Releasing web view.");

        }
    };


    // Obtain a printInfo so that we can set our printing defaults.
    UIPrintInfo *printInfo = [UIPrintInfo printInfo];
    // This application produces General content that contains color.
    printInfo.outputType = UIPrintInfoOutputGeneral;

    printInfo.jobName = self.jobName;
    // Set duplex so that it is available if the printer supports it. We are
    // performing portrait printing so we want to duplex along the long edge.
    printInfo.duplex = UIPrintInfoDuplexLongEdge;
    // Use this printInfo for this print job.
    controller.printInfo = printInfo;

    // Be sure the page range controls are present for documents of > 1 page.
    controller.showsPageRange = YES;


    UIPrintPageRenderer *renderer = [[UIPrintPageRenderer alloc] init];
    renderer.headerHeight = 0.0f;
    renderer.footerHeight = 0.0f;
    controller.printPageRenderer = renderer;


    UIViewPrintFormatter *viewFormatter = [self.myWebView viewPrintFormatter];

    [renderer addPrintFormatter:viewFormatter startingAtPageAtIndex:0];
    // Set our custom renderer as the printPageRenderer for the print job.
    controller.printPageRenderer = renderer;

    [controller presentAnimated:YES completionHandler:completionHandler];  // iPhone


}

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    NSURL *url = [request URL];
    return YES;
}


- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
    // Load error, hide the activity indicator in the status bar.
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;

    // Report the error inside the webview.
    NSString* errorString = [NSString stringWithFormat:
                            @"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\"><html><head><meta http-equiv='Content-Type' content='text/html;charset=utf-8'><title></title></head><body><div style='width: 100%%; text-align: center; font-size: 36pt; color: red;'>An error occurred:<br>%@</div></body></html>",
                             error.localizedDescription];
    [self.myWebView loadHTMLString:errorString baseURL:nil];
}


#pragma mark - Printing

- (void)printWebPage: (NSString *)pathStr jobName:(NSString*)jobName
{
    if(self.myWebView == nil) {
        self.myWebView = [[UIWebView alloc] init];
    }

    self.myWebView.delegate = self;

    if(pathStr == nil) {
        pathStr = [[NSBundle mainBundle] pathForResource:@"checklist" ofType:@"html"];
    }


    self.jobName = @"Print";
    
    if(jobName != nil) {
        self.jobName = jobName;
    }

    [self.myWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:pathStr]]];

}

@end

PrintManager.uno

using Uno;
using Uno.UX;
using Uno.Collections;
using Fuse;
using Fuse.Reactive;
using Fuse.Scripting;
using Fuse.Controls;
using Uno.Compiler.ExportTargetInterop;

[Require("Gradle.Dependency.Compile", "com.android.support:support-v4:19.0.+")]
[ForeignInclude(Language.Java,
  "android.support.v4.print.PrintHelper", "android.graphics.BitmapFactory", "android.graphics.Bitmap")]
[ForeignInclude(Language.ObjC, "WebViewPrinter.h")]
[UXGlobalModule]
public class PrintManager : NativeModule
{
	static readonly PrintManager _instance;

	public PrintManager () {
		if(_instance != null) return;
		Uno.UX.Resource.SetGlobalKey(_instance = this, "PrintManager");

        AddMember(new NativeFunction("printImage", (NativeCallback)PrintImage));
        AddMember(new NativeFunction("printMulti", (NativeCallback)PrintMulti));
        AddMember(new NativeFunction("printUrl", (NativeCallback)PrintUrl));
	}

    object PrintUrl(Context c, object[] args) {

        string url = args[0] as string;
        string jobName = "Print";
        if(args.Length > 1) {
            jobName = args[1] as string;
        }

        debug_log "Printing URL: " + url;
        PrintUrlImpl(url, jobName);

        return null;

    }

    object PrintImage(Context c, object[] args) {
        var item = args[0] as string;

        string jobName = "Print";
        if(args.Length > 1) {
            jobName = args[1] as string;
        }

        PrintImageImpl(item, jobName);

        return null;

    }

    object PrintMulti(Context c, object[] args) {

        Fuse.Scripting.Array pages = args[0] as Fuse.Scripting.Array;
        for(int i = 0; i < pages.Length; ++i) {
            debug_log "Page " + i.ToString() + ": " + (pages[i] as string);
        }

        string jobName = "Print";
        if(args.Length > 1) {
            jobName = args[1] as string;
        }

        string[] pagesImpl = new string[pages.Length];
        for (var i = 0; i < pages.Length; i++) {
            pagesImpl[i] = pages[i] as string;
        }

        PrintMultiImpl(pagesImpl, jobName);

        return null;
    }

    extern(!iOS) public void PrintMultiImpl(string[] files, string jobName)
    {
        debug_log "Printing not supported yet.";
    }

    extern(!iOS) public void PrintUrlImpl(string url, string jobName)
    {
        debug_log "Printing not supported yet.";
    }

    extern(!iOS && !Android) public void PrintImageImpl(string file, string jobName)
    {
        debug_log "Printing not supported yet.";
    }

	[Foreign(Language.Java)]
	extern(Android) public void PrintImageImpl(string file, string jobName)
	@{
		// Currently prints the first page...
	    PrintHelper photoPrinter = new PrintHelper(com.fuse.Activity.getRootActivity());
	    photoPrinter.setScaleMode(PrintHelper.SCALE_MODE_FIT);
	    Bitmap bitmap = BitmapFactory.decodeFile(file);
	    photoPrinter.printBitmap(jobName, bitmap);
	@}

    [Foreign(Language.ObjC)]
    extern(iOS) public void PrintUrlImpl(string url, string jobName)
    @{
        WebViewPrinter * printer = [[WebViewPrinter alloc] init];
        [printer printWebPage:url jobName:jobName];
    @}


    [Foreign(Language.ObjC)]
    extern(iOS) public void PrintMultiImpl(string[] files, string jobName)
    @{
        if([UIPrintInteractionController isPrintingAvailable]){
            UIPrintInteractionController *printController = [UIPrintInteractionController sharedPrintController];
            UIPrintInfo*printInfo = [UIPrintInfo printInfo];
            [printInfo setOutputType:UIPrintInfoOutputGeneral];
            [printInfo setJobName:jobName];
            [printInfo setOrientation:UIPrintInfoOrientationPortrait];
            [printController setPrintInfo:printInfo];
            [printController setShowsNumberOfCopies:NO];
            [printController setShowsPageRange:NO];

            NSMutableArray * printItems = [NSMutableArray arrayWithCapacity:1];
            NSArray * orig = [files copyArray];
            for(id file in orig) {
                NSURL *baseURL = [NSURL fileURLWithPath: file];
                NSLog(@"Will print: %@", baseURL.absoluteString);
                [printItems addObject:baseURL];
            }
			[printController setPrintingItems:printItems];

			dispatch_async(dispatch_get_main_queue(), ^{
	            [printController presentAnimated:YES completionHandler:^(UIPrintInteractionController *printInteractionController, BOOL completed, NSError *error2) {
	                }];
			});
        }
    @}


    [Foreign(Language.ObjC)]
    extern(iOS) public void PrintImageImpl(string file, string jobName)
    @{
        if([UIPrintInteractionController isPrintingAvailable]){
            UIPrintInteractionController *printController = [UIPrintInteractionController sharedPrintController];
            UIPrintInfo*printInfo = [UIPrintInfo printInfo];
            [printInfo setOutputType:UIPrintInfoOutputGeneral];
            [printInfo setJobName:jobName];
            [printInfo setOrientation:UIPrintInfoOrientationPortrait];
            [printController setPrintInfo:printInfo];
            [printController setShowsNumberOfCopies:NO];
            [printController setShowsPageRange:NO];

            NSURL *baseURL = [NSURL fileURLWithPath: file];
            [printController setPrintingItem:baseURL];

            dispatch_async(dispatch_get_main_queue(), ^{
                [printController presentAnimated:YES completionHandler:^(UIPrintInteractionController *printInteractionController, BOOL completed, NSError *error2) {
                }];
            });
        }
    @}


}

Hey!

Thanks for your question.

Without going too much into the specifics of your code, the general way to keep an Objective-C object alive across function calls is to store a reference to it in Uno. Its type will then be ObjC.Object.

So, to keep the object alive, you might have a field in PrintManager such as:

extern(iOS) ObjC.Object _webViewPrinter;

Then you might create a method

extern(iOS) ObjC.Object CreateWebViewPrinter()

that creates the object using foreign code (returning a WebViewPrinter*).

Then you can assign it, e.g. using _webViewPrinter = CreateWebViewPrinter();, at some appropriate point in your code and it will be kept alive until you assign it something else, e.g. with _webViewPrinter = null;.

To use the object in your other foreign methods you can pass it as an ObjC.Object parameter. In the body of the code it’ll then be of it type id, but you can downcast it to WebViewPrinter*.

Hope that helps!