tcoulter / jockeyjs

Library to facilitate communication between iOS apps and JS apps running inside a UIWebView
MIT License
456 stars 100 forks source link

WKWebView Support #27

Open tomerlevi444 opened 9 years ago

tomerlevi444 commented 9 years ago

Are there any plans on supporting iOS8's WKWebView?

geoffharcourt commented 9 years ago

+1 here. The performance gains are pretty incredible, and apps for iOS8 forward are going to increasingly use it.

tcoulter commented 9 years ago

@tomerlevi444: I'm not using this in production as much as I was, so I haven't been adding new features. If you submit a PR, I'm happy to merge it.

geoffharcourt commented 9 years ago

@tcoulter, upon further research, WKWebKitView has some code that's designed to accomplish Jockey's functionality, so it's probably best to tell users to just implement using Apple's built-in functionality rather than complicate the UIWebView code that already exists and performs well.

jordanmaguire commented 8 years ago

This works fine:

In JockeyWK.h

#import <Foundation/Foundation.h>
#import <WebKit/WebKit.h>

@interface JockeyWK : NSObject

typedef void (^ JockeyWKHandler)(NSDictionary *payload);
typedef void (^ JockeyWKAsyncHandler)(WKWebView *webView, NSDictionary *payload, void (^complete)());

+ (void)on:(NSString*)type perform:(JockeyWKHandler)handler;
+ (void)on:(NSString*)type performAsync:(JockeyWKAsyncHandler)handler;
+ (void)send:(NSString*)type withPayload:(id)payload toWebView:(WKWebView*)webView;

+ (BOOL)webView:(WKWebView*)webView withUrl:(NSURL*)url;

@property (strong, atomic) NSNumber *messageCount;
@property (strong, nonatomic) NSMutableDictionary *listeners;
@property (strong, nonatomic) NSMutableDictionary *callbacks;

@end

In JockeyWK.m

#import "JockeyWK.h"

@implementation JockeyWK

+ (id)sharedInstance
{
    static dispatch_once_t once;
    static JockeyWK *sharedInstance;
    dispatch_once(&once, ^{
        sharedInstance = [[self alloc] init];
        sharedInstance.messageCount = @0;
        sharedInstance.listeners = [[NSMutableDictionary alloc] init];
        sharedInstance.callbacks = [[NSMutableDictionary alloc] init];
    });
    return sharedInstance;
}

+ (void)on:(NSString*)type perform:(JockeyWKHandler)handler {
    void (^ extended)(WKWebView *webView, NSDictionary *payload, void (^ complete)()) = ^(WKWebView *webView, NSDictionary *payload, void(^ complete)()) {
        handler(payload);
        complete();
    };

    [self on:type performAsync:extended];
}

+ (void)on:(NSString *)type performAsync:(JockeyWKAsyncHandler)handler
{
    JockeyWK *instance = [JockeyWK sharedInstance];

    NSDictionary *listeners = [instance listeners];

    NSMutableArray *listenerList = [listeners objectForKey:type];

    if (listenerList == nil) {
        listenerList = [[NSMutableArray alloc] init];

        [instance.listeners setValue:listenerList forKey:type];
    }

    [listenerList addObject:handler];
}

+ (void)send:(NSString*)type withPayload:(id)payload toWebView:(WKWebView*)webView {
    JockeyWK *jockey = [JockeyWK sharedInstance];

    NSNumber *messageId = jockey.messageCount;

    NSError *err;
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:payload options:NSJSONWritingPrettyPrinted error:&err];
    NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
    NSString *javascript = [NSString stringWithFormat:@"Jockey.trigger(\"%@\", %i, %@);", type, (int)[messageId integerValue], jsonString];

    [webView evaluateJavaScript:javascript completionHandler:nil];

    jockey.messageCount = @([jockey.messageCount integerValue] + 1);
}

+ (BOOL)webView:(WKWebView*)webView withUrl:(NSURL*)url
{
    if ( [[url scheme] isEqualToString:@"jockey"] )
    {
        NSString *eventType = [url host];
        NSString *messageId = [[url path] substringFromIndex:1];
        NSString *query = [url query];
        NSString *jsonString = [query stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];

        NSError *error;
        NSDictionary *JSON = [NSJSONSerialization JSONObjectWithData: [jsonString dataUsingEncoding:NSUTF8StringEncoding]
                                                             options: NSJSONReadingMutableContainers
                                                               error: &error];

        if ([eventType isEqualToString:@"event"]) {
            [[self sharedInstance] triggerEventFromWebView:webView withData:JSON];
        } else if ([eventType isEqualToString:@"callback"]) {
            [[self sharedInstance] triggerCallbackForMessage:@([messageId integerValue])];
        }

        return NO;
    }
    return YES;
}

# pragma mark - Private methods

- (void)triggerCallbackForMessage:(NSNumber *)messageId {
    NSString *messageIdString = [messageId stringValue];

    void (^ callback)() = [_callbacks objectForKey:messageIdString];

    if (callback != nil) {
        callback();
    }

    [_callbacks removeObjectForKey:messageIdString];
}

- (void)triggerCallbackOnWebView:(WKWebView*)webView forMessage:(NSString*)messageId
{
    NSString *javascript = [NSString stringWithFormat:@"Jockey.triggerCallback(\"%@\");", messageId];
    [webView evaluateJavaScript:javascript completionHandler:nil];
}

- (void)triggerEventFromWebView:(WKWebView*)webView withData:(NSDictionary*)envelope
{
    NSDictionary *listeners = [[JockeyWK sharedInstance] listeners];

    NSString *messageId = [envelope objectForKey:@"id"];
    NSString *type = [envelope objectForKey:@"type"];

    NSDictionary *payload = [envelope objectForKey:@"payload"];

    NSArray *listenerList = (NSArray*)[listeners objectForKey: type];

    __block NSInteger executedCount = 0;

    void (^complete)() = ^() {
        executedCount += 1;

        if (executedCount >= [listenerList count]) {
            [[JockeyWK sharedInstance] triggerCallbackOnWebView: webView forMessage: messageId];
        }
    };

    for (JockeyWKAsyncHandler handler in listenerList) {
        handler(webView, payload, complete);
    }
}

@end

and just interact with it the same way you'd interact with Jockey usually.

I don't know the exact version of Jockey I'm interacting with tho. From 2013 at least.