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.

September 10, 2011

At Last!!!

Posted in Android Development, iPhone development tagged , , , , , , , , , , , , , , , at 10:29 pm by tetontech

At long last (big sigh of relief here by me) QuickConnectFamily 2.1 is finally available for download.  It involved a lot of work by many people and has come together well.  There are some big changes for both the iOS and Android platforms.  These enhancements and changes have been driven by requests from developers like you.

Both:

  • This one is BIG.  The JavaScript functions now exist inside the qc name space.  In other words where you would have used the handleRequest method it is now the qc.handleRequest method.  The old behavior is deprecated.
  • Another BIG one.  In order to make the Control Function code more readable and more easily comprehended for those new to the framework all Control Functions MUST return one of the following three values (see the documentation for more information):
    • qc.STACK_CONTINUE – This instructs the framework to execute the next control function in the stack.
    • qc.STACK_EXIT – This instructs the framework to terminate all further stack execution.
    • qc.WAIT_FOR_DATA – This instructs the framework that a call to database or remote data has been made or a call to device specific behavior such as showing a map, displaying the camera, email editor or other native behaviors.
  • Work has been done to improve the asynchronous call stability in the underlying framework.  (Thank you to the team at affinityAmp).
  • Miscellaneous bug fixes and enhancements.

Android:

  • Bug fixes
  • Expanded database support and fixes
  • A major rework of the underlying Android Java code to make it match the design changes in iOS.  This is in preparation for QC Plugins and some new features such as ‘window-in-window’ that will be part of the next release as a Plugin.  The ‘window-in-window’ code is in there now but not official until it is converted to a plugin and the same behavior is available for iOS.
  • Added a hybrid sqlite database example

iOS:

  • Bug fixes
  • Removed the native footer code since libraries for scrolling and others such as Sencha, JQTouch, etc. are now of good quality.
  • QC Family Hybrid Plugin API and design spec completed.  There is an example of how you can add to QC on your own.  If you thing these additions could be useful to others you are free to charge for them, or not, host them yourself, notify me and I will add them to the plugin list on the QC plugin site.  If you are willing to donate them to the QC community send them to me for review and I will put them into the git repository and list them on the QC plugin site.
  • Updated all the examples to use the new return values and the new qc name space.
Now that this is out I will be creating a Key/Value store for Android and presenting it at the next AnDevCon.  I’m also looking forward to adding Android build support back into the QC Hybrid build process so you can write your app once yet build and run it in the iOS simulator and the Android emulator with one click.
As always I hope this is helpful for you.  I enjoy working on QC and hope to be able to do so long into the future.  If you find any issues please let me know via the google group.
Lee

August 16, 2011

QC DBSync version 1.3 available

Posted in Uncategorized tagged , , , , , , , , , , at 5:24 pm by tetontech

For those of you needing a native database synchronization tool version 1.3 of QC DBSync is now available.  It includes minor functionality and API upgrades on the iOS side, an example service written in PHP is included in the download.

QC Native 1.3 available

Posted in Uncategorized tagged , , , , , , , , , , at 3:30 am by tetontech

For those of you doing native, not hybrid JavaScript, development with QC I have uploaded a new version of QC Native.  It rationalizes the differences between the Java and iOS versions making the APIs nearly identical.  I have also updated the API Docs for Android, created API Docs for iOS, and included both in the downloads as well as the QC Family web site.

The download now includes a SimpleDB example for both Android and iOS.  The example inserts values into the database, queries values from the database, and can do an HTTP GET.  The iOS database interactions show how to use CoreData.  I will soon (by the end of the month??) have an example using the Android ORM I’m developing.

Still working on that QC Hybrid release.  It is getting really close.  More on that later.

May 11, 2011

iPad and Showing the camera in Objective-C

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

So here is a hint for using the camera with the iPad.

The iPad allows the camera view to rotate as the orientation of the device changes.  Imagine this set of views.

window

———-> base view

—————> subview

————–> camera view

If you display the camera from the subviews’ view controller the camera views’ control bar and the orientation of the camera view itself will only be correct if  the device is in portrait-right-side-up orientation when the camera view is displayed.  In any other orientation the camera view will be displayed incorrectly and this orientation error remains as the device orientation changes.

If the set of views is like this the camera view will be displayed correctly regardless of the original orientation of the device.  It will remain correct as the orientation of the device changes.

window

———-> base view

—————> camera view

So here is a rule for showing the camera via the UIImagePickerControllerDelegate class.

“The view controller that displays the camera must be controlling a view whose superView is the main window if the controlled view auto rotates.”

January 6, 2011

QuickConnect and the Mac app store

Posted in misc tagged , , , , , , at 7:50 pm by tetontech

The mac app store is up.  Do you want to put an app up?  QC has supported hybrid mac development for two years.  It is ready for you now.  All you have to do is make your selection as you see in the picture below and start making your app.

I’m currently adding more functionality to QC Mac.  The update should be out next week but you can get stared now.  Develop your app in HTML, CSS, and JavaScript just like your hybrid iOS, Android, Blackberry apps.

Selecting QC for the Mac

QuickConnect 1.6.6 now available

Posted in iPhone development tagged , , , , , , at 3:05 am by tetontech

QC 1.6.6 has a fixed installer but also has an upgrade that should make your user interface more responsive when you use the framework calls to download or upload files, make native database queries, or any other calls you make down the the native layer.  You don’t need to worry about the threading issues or starting the threads.  It is all automatic.  It doesn’t have any impact on your JavaScript code.

December 31, 2010

Sencha Touch and QuickConnect Hybrid

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

Ed Canas has put together a short video on how to use Sencha Touch with QuickConnect for the iPhone and iPad. You can find the video here on the QuickConnect wiki.  In seven minutes he has the Sencha Kitchen sink example working with the QuickConnect JavaScript functions still available.

Thanks Ed.

Go take a look.

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

July 4, 2010

QuickConnectFamily 1.6 Release is Here.

Posted in Android Development, Blackberry development, iPhone development tagged , , , , , , , , at 2:04 am by tetontech

The first non-beta of QuickConnect 1.6 is now available.

Thanks to several of your users the new QC release is available.  It is no longer in Beta!

It includes an update to Android version 2.2.  You will also find that the template selection has been dramatically simplified.  You will find that there are now two template groups, QuickConnect Mobile Hybrid and QuickConnect Mobile Obj-C.  Within these groups you will find the different types of projects.

For the Hybrid group you will find iPhone, iPad, Android, Blackberry, and PalmWebOS hybrid JavaScript/Objective-C templates.

For the Obj-C group you will find iPhone and iPad Objective-C  templates.

The Obj-C group is used to develop pure Objective-C iPhone and iPad applications.

There have been many changes and improvements since 1.5 including many more examples.

Next page

<span>%d</span> bloggers like this: