July 17, 2014

Objective-C WKWebView to JavaScript and Back

Posted in iPhone development, mac development tagged , , , , at 4:50 pm by tetontech

 In a previous post I showed how to communicate from JavaScript to Swift and back.  This code example shows how to use the new WKWebView rather than the old UIWebView with Objective-C.

Since WKWebView doesn’t yet show up as a drag-and-droppable view in Interface Builder, and other IB work-arounds cause the app to crash, a WKWebView instance is created in the ViewController’s viewDidLoad method. The WKWebView instance is then sized to fill the entire view of the device. This can cause usability problems since the WKWebView’s content now overlaps the header bar. If I was creating an app to ship, I would use Interface Builder to add a UIView, create an IBOutlet to that view, and size the UIView to fit the display portion of the screen. I would then add the WKWebView to the UIView I added using Interface Builder.

The header file for the ViewController class shows how to include the WebKit headers and make the ViewController a WKScriptMessageHandler. The ViewController needs to be a WKScriptMessageHandler since, in this example, the ViewController is going to be sent messages from the JavaScript code.

#import <UIKit/UIKit.h>
#import <WebKit/WebKit.h>
@interface ViewController : UIViewController <WKScriptMessageHandler>
   
@end

In order to create the WKWebView and add it to the main view in viewDidLoad and later use the same WKWebView in userContentController:didReceiveScriptMessage method a WKWebView property called theWebView is added to the ViewController class.

The ViewController’s viewDidLoad method includes the creation of an instance of WKWebViewConfiguration. Among other things, WKWebViewConfiguration is used to setup and name the JavaScript message listener. This setup is done with the WKWebViewConfiguration userContentController’s addScriptMessageHandler:name method. For this example I’ve chosen ‘interOp’ as the name to be exposed to JavaScript for the message handler and used the ViewController as the message handler.

Once WKWebKit is debugged, the following code should work. It currently doesn’t (iOS 8.1) so I’ll show you a work-around right after this “is supposed to work but doesn’t code example.”

#import "ViewController.h"

@interface ViewController ()
    @property(strong,nonatomic) WKWebView *theWebView;
@end

@implementation ViewController
            
- (void)viewDidLoad {
    [super viewDidLoad];
    NSString *path = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"];
    NSURL *url = [NSURL fileURLWithPath:path];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    
    WKWebViewConfiguration *theConfiguration = 
          [[WKWebViewConfiguration alloc] init];
    [theConfiguration.userContentController 
          addScriptMessageHandler:self name:@"interOp"];
    
    _theWebView = [[WKWebView alloc] initWithFrame:self.view.frame 
                      configuration:theConfiguration];
    [_theWebView loadRequest:request];
    [self.view addSubview:_theWebView];
    
}

Since ViewController is a WKScriptMessageHandler, as declared in the ViewController interface, it must implement the userContentController:didReceiveScriptMessage method. This is the method that is triggered each time 'interOp' is sent a message from the JavaScript code.
- (void)userContentController:(WKUserContentController *)userContentController 
                            didReceiveScriptMessage:(WKScriptMessage *)message{
    NSDictionary *sentData = (NSDictionary*)message.body;
    long aCount = [sentData[@"count"] integerValue];
    aCount++;
    [_theWebView evaluateJavaScript:[NSString 
            stringWithFormat:@"storeAndShow(%ld)", aCount] completionHandler:nil];
}

In the example code above you can see a WKScriptMessage is received from JavaScript. Since WKWebKit defines JSON as the data transport protocol, the JavaScript associative array sent as the message's body has already been converted into an NSDictionary before we have access to the message. We can then use this NSDictionary to retrieve an int that is the value associated with the 'count' label. The JSON conversation creates NSNumbers for numeric type values so the code example retrieves the NSNumber's intValue, modifies it, and then sends the modified value back to JavaScript. 

One of the very nice features of WKWebKit framework is that the WKWebView runs independently of the main, or User Interface, thread. This keeps our apps responsive to user input. The storeAndShow method will not execute in the app's main thread. 

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

The JavaScript examples below show how to send a message to Objective-C using webkit’s list of messageHandlers and the interOp handler we setup in the ViewController’s viewDidLoad method. An example of this is found in the sendCount JavaScript function. The storeAndShow function that is triggered is strait forward. All that function does is some standard JavaScript using the DOM (Document Object Model).

var count = 0

function sendCount(){
    var message = {"count":count}
    window.webkit.messageHandlers.interOp.postMessage(message)
}

function storeAndShow(updatedCount){
    count = updatedCount
    document.querySelector("#resultDisplay").innerHTML = count
}

Overall I am really pleased with the new WKWebView and the other classes in the WKWebView framework when it is compared to the old UIWebView. I like the WKWebView executing outside the main thread. I like the choice of JSON for the data transfer protocol. The adding of the message handlers is a little awkward, but nothing is ever perfect.

When you try to run this “should work but doesn’t code” the screen on your device will be blank. There is a scrollbar that you can move but the page is never displayed. This code does work on a device when written in Swift if you move all of the web components to the app’s temp directory before you build the NSURL. For the work-around we’ll do the same thing in Objective-C. I’ve written a small Objective-C class called WebMover to do that for you. It will move web files and any of you app’s web directories for you. When it does the move it will return the path to the index.html file to you so you can use it to create a NSURL. I won’t cover that code in this posting. You can get WebMover’s source and an example project from my gitHub ObjectivelyHybrid repo.

I have tracked down one of the issues that causes Objective-C to fail to load the page, even when the web files have been copied to the app’s temp directory. The problem is with this line of code.

NSURL *url = [NSURL fileURLWithPath:path];

If this line of code is executed, a NSURL is generated and appears to be good in all observable respects. The NSURL is defective. If the same line of code is executed in Swift, the defect disappears. Because of this, the work-around will generate the NSURL in Swift, but the rest of the code will remain Objective-C.

The working viewDidLoad method now looks like this.

@implementation ViewController
            
- (void)viewDidLoad {
    [super viewDidLoad];
    NSError *moveError = nil;
    //modify the array of file types to fit the web file types your app uses.
    NSString *indexHTMLPath = [WebMover moveDirectoriesAndWebFilesOfType:
                               @[@"js",@"css",@"html",@"png",@"jpg",@"gif"] error:&moveError];
    if (moveError != nil) {
        NSLog(@"%@",moveError.description);
    }
    //this is an Objective-C call to some Swift code.
    NSURL *url = [SwiftlyBridge buildURL:indexHTMLPath];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    
    WKWebViewConfiguration *theConfiguration = 
          [[WKWebViewConfiguration alloc] init];
    [theConfiguration.userContentController 
          addScriptMessageHandler:self name:@"interOp"];
    
    _theWebView = [[WKWebView alloc] initWithFrame:self.view.frame 
                      configuration:theConfiguration];
    [_theWebView loadRequest:request];
    [self.view addSubview:_theWebView];
    
}

The thing I like about this work-around is how close it is to the original “should work” solution. Unlike others who have suggested highly complicated things like building a web server into your app.

The SwiftlyBridge Code is also tiny. In the example project you can find it in the SwiftlyBridge.swift file.

import Foundation
import WebKit

@objc class SwiftlyBridge {
    
    class func buildURL(indexHTMLPath:String) ->NSURL{
        return NSURL(fileURLWithPath: indexHTMLPath)!
    }
}

When you add this file to your project, Xcode will ask you if you want it to create a bridging header file. The answer is an emphatic YES. Make sure you have it do this for you.

Once you’ve added the WebMover.m and .h files and the SwiftlyBridge.swift file maker SURE that you attempt to build your project. It will fail, but the build process will generate a .h, header, file for you that will allow you to import the SwiftlyBridge class. In the example project this generated header file is imported in to the view controller source, you can compile and run.

The name of the generated header is always <ProjectName>-Swift.h. The Example project’s name is ObjectivelyHybridiOSExample so the import call is

#import <ObjectivelyHybridiOSExample-Swift.h>

I’ve had trouble with projects that have spaces or dashes in there names. In those cases Xcode doesn’t seem to be able to find the generated header file.

 

That should do it. Post if you are having issues.

Advertisement

July 15, 2014

Swift to JavaScript and JavaScript to Swift. A round trip.

Posted in Uncategorized tagged , , , , , at 9:12 pm by tetontech

Edit: Since originally writing this post I have Created the SwiftlyHybrid and AndyHybrid libraries and put them on gitHub. If you are interested in the topic discussed here I would suggest also reading the SwiftlyHybrid and AndyHybrid follow up posting.

In a previous post I showed how to make a call from JavaScript to Swift. Take a look there for a more in-depth discussion of the code to accomplish that data transfer. This example expands on what I did in that post by showing how to make a call from JavaScript to Swift, do a calculation, and then pass the results back to JavaScript. The source code is very similar. It uses one method, evaluateJavaScript(), of the WKWebView class that wasn’t available in Apple’s previous Swift beta.

In the view controller’s userContentController method we’ll need to have access to the WKWebView created in the viewDidLoad method. This requires the addition of a WKWebView attribute to the ViewController class. After trying a couple of experimental ideas I remembered the correct, Swifty way to do this. I couldn’t create the WebView on the same line as the attribute was declared since the initializer for the WKWebView isn’t as simple as adding some integer attribute (var someInt = 3). Instead the initializer needs a frame and a WKWebViewConfiguration. Because of this the WKWebView must be initialized in the viewDidLoad method (This may change if Apple adds a WKWebView to Interface Builder).

The Swift way to declare an attribute and initialize it in something other than an init method is to make the attribute optional using the ? operator.

class ViewController: UIViewController, WKScriptMessageHandler {
    
    var theWebView: WKWebView?
}

In the viewDidLoad method the WKWebView can now be created using the WKWebView function. Once theWebView has been set, the ! operator is needed to declare that we know theWebView attribute is not nil.

(Warning: the functionallity of WKWebView has changed since this code was written. You must move all resources (html, JavaScript, Css, images, and other files) into the temp directory or WKWebView will not load the resources. This may change in the future as WebKit changes or it might not.)

    override func viewDidLoad() {
        super.viewDidLoad()
        var path = NSBundle.mainBundle().pathForResource("index", 
                               ofType: "html")
        var url = NSURL(fileURLWithPath: path)
        var request = NSURLRequest(URL: url)

        var theConfiguration = WKWebViewConfiguration()
        theConfiguration.userContentController.addScriptMessageHandler(self, 
                                   name: "interOp")

        theWebView = WKWebView(frame: self.view.frame, 
                             configuration: theConfiguration)
        theWebView!.loadRequest(request)
        self.view.addSubview(theWebView)

    }

As in the previous example, the userContentController() method must be implemented to handle the messages captured by the script message handler that was added to theConfiguration. In this example the message’s body, the data sent from the JavaScript is interpreted as an NSDictionary since the JavaScript sends an associative array.

    func userContentController(userContentController: 
       WKUserContentController!,
       didReceiveScriptMessage message: WKScriptMessage!) {
        
        let sentData = message.body as NSDictionary
        let aCount:Int = Int(sentData["count"] as NSNumber)
        
        theWebView!.evaluateJavaScript("storeAndShow( \(aCount + 1) )", 
                                        completionHandler: nil)
    }

The other change to the previous example is the addition of the call to theWebView’s evaluateJavaScript method. It has two parameters; a string that is the Javascript to be executed, and a function or closure that will be notified once the JavaScript completes. In this example I don’t need to know when the JavaScript completes so I passed nil as the second parameter.

The javascript for this example is strait forward; an onclick listener function to send a message to the Swift message handler, and the storeAndShow function that the Swift code will call to send a message back to the JavaScript.

var count = 0

function sendCount(){
    var message = {"count":count}
    window.webkit.messageHandlers.interOp.postMessage(message)
}

function storeAndShow(updatedCount){
    count = updatedCount
    document.querySelector("#resultDisplay").innerHTML = count
}

For my own code I would send a Dictionary back to the JavaScript where it would be used as an associative array. I left that out of this example since I didn’t think it was necessary to show.

Overall, the interaction between Swift and Javascript using the WKWebView is more strait forward than using the old UIWebView. It is nice to have a JavaScript way to directly call to Swift. I also like the selection of JSON as the data transfer format for the JS to Swift communication. It reflects the decision I made to adopt that format for QCHybrid years ago. It would be better if the values in the dictionary knew their type so casting using the as operator wasn’t needed but I can live with it the way it is.

June 12, 2014

Swift Hybrid Development

Posted in iPhone development, mac development tagged , , , , , , , , , , , , at 3:02 pm by tetontech

From 2007 until 2012 when my doctoral degree got in the way I created and then supported a hybrid development environment call QC Hybrid. It was designed to allow you to build an installable application in Javascript, HTML, and CSS. It also allowed you to make calls and pass JSON data from Javascript down to the native execution code, Objective-C for iOS and Java for Android.

My doctoral degree is now done. I could begin supporting that code set again but it would be better to implement it in Swift or current Objective-C and gain access to the new WebKit API  common to both iOS 8 and OS X Yosemite. Another reason to ‘start from scratch’ is that the new WKWebView is much more powerful that the old UIWebView. According to Apple, it has all the new HTML 5 abilities and now the same webkit engine as Safari so tools like QC Hybrid, PhoneGap, and the other hybrid tools are not needed by the vast majority of Hybrid developers. That was not true in the past. Never the less, I maintain that this type of project should, in most cases, be a temporary and stop-gap solution while you develop your app in the native languages of the platform. At least for now.

With all that history out of the way, lets get to a Swift code example.

For a hybrid application, the HTML, Javascript, CSS, and image files are included as part of the project just like any other file. This image shows the single-view Swift project I created for this posting. Also make sure you add the WebKit.framework to your project.

project

You can see I have a file called index.html. This is the file that the example code is going to load and trigger the loading of the main.css and main.js files just like any normal web page.

In the project’s ViewController.viewDidLoad() method I inserted added code to create a WKWebView, load index.html, and add the WKWebView to the main view.

(Note: This code example has been updated for Swift 2.0 on my gitHub repository.)

        //get a string path to the file
        var path = NSBundle.mainBundle().pathForResource("index",
                             ofType: ".html")
        //create a URL for the path
        var url = NSURL(fileURLWithPath:path)
        //create a request for the URL
        var request = NSURLRequest(URL:url)
        
        //create the WKWebView and set the size to match 
        //the window's full view 
        var theWebView:WKWebView = WKWebView(frame:self.view.frame)
        //have the web view load the page
        theWebView.loadRequest(request)
        //add the web view to the main view
        self.view.addSubview(theWebView)

The reason I started the load before adding the web view was to get a little buffering going on before the display. The web view will load without being displayed.

I’m going to do another posting on how to communicate between Swift and Javascript in a later post. That has changed dramatically as well.

December 23, 2010

QuickConnect 1.6.4 Now Available

Posted in Uncategorized tagged , , , , , , , , , , , , , , , at 8:44 am by tetontech

I have just posted the 1.6.4 version of QC on SourceForge.  It includes some defect fixes and some minor additions, and a few big changes.  QC 1.6.4 requires the iOS 4.2 SDK.

The big changes are regarding the native application templates.  You can now use the same design to create Objective-C iPhone, iPad, and Universal iPhone/iPad apps that you have been using to create your hybrid applications.

These native iOS apps come ‘pre-threaded’.  Every time you call handleRequest your command stack is executed on a worker thread.  Any of your ViewControlObjects that you create for your control Stack are executed in the main  thread since it is the only one that is allowed to update the User Interface.  All other behavior is done on a worker thread and you don’t have to worry about how to set it up, make it go, or make it stop.

Just as with the hybrid apps you’ve been creating with QC all of your async calls to HTTP servers, portals, etc. are linearized for you.  You never need to write another callback function!

In addition to making your remote HTTP calls easier all of the templates for native QuickConnect applications also include support for both direct SQLite access and CoreData.

With a little time working in Interface Builder and putting together some CoreData objects your app is up and running.

Examples are already in the download for all of these native iOS templates.  Check them out and see how easy native iOS apps can be.

The next release will have native multi-threaded Android applications as well.

One other change is  that the PHP template has been updated.  Take a look at the example in the download.

Lee

September 24, 2009

QuickConnectiPhone 1.6 Beta 1 is now available

Posted in iPhone development tagged , , , , , , , , , , , , , at 9:42 pm by tetontech

QCiPhone 1.6 beta 1 is now available from the sourceForge repository.  It includes access to the Contacts API via JavaScript as well as a new way of doing GPS locations and tracking.  There are two new examples to show how to use the new functionality, GPSExample and Contacts Example.

The old in-application map functionality has been commented out in anticipation of Beta 2 which will include the access to the new 3.0 Map API and in-application email.

Push from within JavaScript and receiving pushes in JavaScript planned for Beta 3.

August 29, 2009

QuickConnectiPhone 1.5.0 is now available

Posted in erlang development, iPhone development, mac development, Uncategorized tagged , , , , , , , , , , , , , , , at 9:05 pm by tetontech

QCiPhone 1.5.0 has just been uploaded to sourceForge.  They are saying that it may take about 15 minutes to become visible as the default download for OS X machines.

A note to 3.0 API users. The dashcode QC template no longer appears as an option.  I am looking for ways to get it to show up again.End

Because of the 3.0 Dashcode changes I have not been able to update the Dashcode examples to the latest QC source.  All of the Xcode examples are updated.

The release includes examples of how to use the video and audio tags in booth QCiPhone and QCMac applications.

QuickConnectMac 1.0 is also included in this release.  It allows you to create hybrid applications that run on Mac machines just like you do for the iPhone and iPod touch.

QuickConnectPHP 1.0 is another template that can be used on the Mac side.  It isn’t used to create hybrid applications but is used to create PHP web applications.

Also included, for those interested, is QCErlang 1.0.  It is an updated version of an Xcode template for developing erlang applications.  It includes auto-completes for most of the commonly used functions.

Defect fixes in this release include:

1.  UIWebView no longer ends up with a black background after re-displaying the Default.png file while the page loads.

2.  DataAccessObject in-browser database use fixed and updated to new methodology for the retention of which BCF in the stack is to be called next.

3.  Updated the embedded map code to use the current data passing scheme from and to Objective-C

4.  HTML Elements with touch events inside of Elements made scrollable no longer react to ontouchstart, ontouchmove, and ontouchend unless the event is not due to scrolling

August 28, 2009

HTML 5 Video and Audio in UIWebView and WebView

Posted in iPhone development, mac development tagged , , , , , , , , , , , , , , , , at 9:01 pm by tetontech

I have been playing with the video and audio tags in the UIWebView on the iPhone and WebView on the Mac.  I tried them in the QT WebView as well on linux.  I am pleased to announce that they work great!  I have tried it with mp3, mp4, and wmv.  All worked on all three platforms.

It looks like this is a good way to display videos for your users to play in your hybrid applications.

Let’s say you have a video called someCrazyMovie.mp4 that you want to display to your users.  On the iPhone or the Mac put it in the Resources group of your Xcode project that uses a UIWebView.  Point the UIWebView to a local html file, also in the resources file.

In this html file put the following code.

<video src=”someCrazyMovie.mp4>A movie description as an alt</video>

When you run your application you will see the first frame of the video used as a representational image.  You can play it by clicking it.  On the iPhone and iPod touch the movie player launches to play the movie.

The tag lets you size it, display or not display the video controls, etc.

The audio tag is used much the same way.

I’ll include an example for both the iPhone and the Mac in the 1.5.0 release of QuickConnectiPhone 1.5.0

August 4, 2009

New License

Posted in iPhone development tagged , , , , , , , at 5:59 pm by tetontech

Because of the confusion over the LPGP license of QuickConnectiPhone it is now available under the MIT license.  It is still open source and free to use to create either open or closed, free or for-cost applications.

I hope this clears up any licensing issues you may have had.

July 31, 2009

QuickConnect 1.5 Release Candidate 1 now Available!

Posted in iPhone development tagged , , , , , , , , , , , , at 10:38 pm by tetontech

After many setbacks, like a very major hard drive crash and a bug in sourceForge, I have finally been able to release QC 1.5 Release Candidate 1.  It is now the default download for the project on sourceForge.

It includes:

  • Transaction handling for native databases from the JavaScript DataAccessObject.  The example project, NativeDatabaseTransactions, includes:
    • start transaction
    • commit
    • and rollback
  • A fix to the native footers disappearing when the orientation changed
  • Synced the Dashcode project with the latest code and fixed the run error caused by the rotating image
  • A fix that correctly handles all types of URLs such as mailTo, http, https, maps, youtube, etc.
  • A total of 32 QCiPhone examples
    • not all of the examples have been updated to the latest code set.  That will be completed for the final release.  If you are willing to volunteer to help with updating the examples please contact me.

May 14, 2009

UIWebView and Native Footers

Posted in iPhone development tagged , , , , , , , , , , , at 7:18 pm by tetontech

At the request of a couple of QuickConnectiPhone users I have been playing around with a way to combine native footers and/or headers with the UIWebView and specifically the QuickConnectiPhone framework.

I am pleased to say that I have something that should make this easy.  As of the next release of QCiPhone you can create native, Objective-C based footers and buttons for the footers.  You can also hide and display them and the UIWebView containing your QCiPhone application will be resized so that the footer doesn’t cover any of your display.  When you hide the footer the UIWebView expands to fill the space made available by not displaying the footer.

Here is a snapshot of the QCiPhone example app without the footer displayed.

The native footer example application before the Show Footer button is pressed.

The native footer example application before the Show Footer button is pressed.

Here is the native footer being displayed.

The example application after the Show Footer button is pressed.

The example application after the Show Footer button is pressed.

The button is an HTML button in the UIWebView and JavaScript calls are made to show and hide the native footer.

Here is the code from the JavaScript onload event handler that creates the native footer and buttons and then assigns the buttons to the footer for display.  Notice that the last button created uses an image instead of text for it’s cue to the user.

footer = new QCNativeFooter(‘mainFooter’, ‘black’, false);

//examples of using full JavaScript function calls with staticly defined parameters

var lineButton = new QCNativeButton(‘line’, ‘Line’, ‘displayName(“Line”)’, false);

var pieButton = new QCNativeButton(‘pie’, ‘Pie’, ‘displayName(“Pie”)’, false);

var barButton = new QCNativeButton(‘bar’, ‘Bar’, ‘displayName()’, false);

var imageButton = new QCNativeButton(‘image’, ‘puzzleIcon.png’, ‘displayName(“Image Button”)’,true);

footer.setButtons([lineButton, barButton, pieButton, imageButton]);

The QCNativeFooter constructor is defined as in this following line of code.

function QCNativeFooter(uniqueId, color, translucentFlag)

It takes three parameters

  1. A unique identifier for the footer.  You can have as many footers as you wish.
  2. The color of the desired footer as a string.  The iPhone options are ‘black’ and ‘standard’.  Standard is blue.
  3. A boolean flag indicating if the footer should be semi-transparent.

The code to display the footer is seen here.

footer.show();

It is all that is needed to display the footer and automatically resize the display of your application.  To hide the footer simply call

footer.hide();

To change buttons being displayed all you need to do is call the QCNativeFooter object’s setButtons method and pass in an array of buttons that you want to replace the old ones.  The first code snippet in this posting has an example of this call.

These methods and objects are just facades for calls to Objective-C using the QCiPhone framework.  A complete description of all of the Objective-C executed for these calls would be to large for a blog posting.

This example application, as well as a couple of others, will be added to the next version of QCiPhone.  The framework’s template will also be modified to contain all of the Objective-C and JavaScript to allow you to make the calls described in this posting.

Next page

%d bloggers like this: