สะพาน JavaScript ของ iOS


101

ฉันกำลังทำงานกับแอพที่ฉันจะใช้ทั้ง HTML5 ใน UIWebView และเฟรมเวิร์ก iOS ดั้งเดิมร่วมกัน ฉันรู้ว่าฉันสามารถใช้การสื่อสารระหว่าง JavaScript และ Objective-C ได้ มีห้องสมุดใดบ้างที่ทำให้การสื่อสารนี้ง่ายขึ้น ฉันรู้ว่ามีไลบรารีมากมายที่จะสร้างแอพ iOS ดั้งเดิมใน HTML5 และ javascript (เช่น AppMobi, PhoneGap) แต่ฉันไม่แน่ใจว่ามีไลบรารีที่จะช่วยสร้างแอพ iOS ดั้งเดิมที่มีการใช้งาน JavaScript มากหรือไม่ ฉันจำเป็นต้อง:

  1. ดำเนินการเมธอด JS จาก Objective-C
  2. เรียกใช้เมธอด Objective-C จาก JS
  3. ฟังเหตุการณ์ JS ดั้งเดิมจาก Objective-C (ตัวอย่างเช่นเหตุการณ์ที่พร้อมใช้งาน DOM)

1
คุณสามารถใช้ WKWebView: call จาก javascript window.webkit.messageHandlers. {NAME} .postMessage (ข้อความ) จากนั้นจัดการด้วย [WKUserContentController addScriptMessageHandler: name:] เพื่อเรียก Objective-C จาก JS
kostyl

คำตอบ:


150

มีห้องสมุดอยู่สองสามแห่ง แต่ฉันไม่ได้ใช้สิ่งเหล่านี้ในโครงการใหญ่ ๆ ดังนั้นคุณอาจต้องการทดลองใช้:

-

อย่างไรก็ตามฉันคิดว่ามันง่ายพอที่คุณจะลองดูด้วยตัวเอง ฉันทำแบบนี้เป็นการส่วนตัวเมื่อฉันต้องการทำเช่นนั้น คุณอาจสร้างห้องสมุดง่ายๆที่เหมาะกับความต้องการของคุณ

1. ดำเนินการเมธอด JS จาก Objective-C

นี่เป็นโค้ดเพียงบรรทัดเดียว

NSString *returnvalue = [webView stringByEvaluatingJavaScriptFromString:@"your javascript code string here"];

รายละเอียดเพิ่มเติมเกี่ยวกับการอย่างเป็นทางการเอกสาร UIWebView

2. ดำเนินการเมธอด Objective-C จาก JS

น่าเสียดายที่สิ่งนี้ซับซ้อนกว่าเล็กน้อยเนื่องจากไม่มีคุณสมบัติ windowScriptObject (และคลาส) เดียวกันกับที่มีอยู่ใน Mac OSX ทำให้สามารถสื่อสารระหว่างทั้งสองได้อย่างสมบูรณ์

อย่างไรก็ตามคุณสามารถเรียกใช้ URL ที่กำหนดเองด้วยจาวาสคริปต์เช่น:

window.location = yourscheme://callfunction/parameter1/parameter2?parameter3=value

และสกัดกั้นจาก Objective-C ด้วยสิ่งนี้:

- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType {
   NSURL *URL = [request URL]; 
   if ([[URL scheme] isEqualToString:@"yourscheme"]) {
       // parse the rest of the URL object and execute functions
   } 
}

สิ่งนี้ไม่สะอาดเท่าที่ควร (หรือโดยใช้ windowScriptObject) แต่ใช้งานได้

3. ฟังเหตุการณ์ JS ดั้งเดิมจาก Objective-C (เช่น DOM ready event)

จากคำอธิบายข้างต้นคุณจะเห็นว่าหากต้องการทำเช่นนั้นคุณต้องสร้างโค้ด JavaScript แนบกับเหตุการณ์ที่คุณต้องการตรวจสอบและเรียกการโทรที่ถูกต้องwindow.locationเพื่อให้ถูกดักฟัง

อีกครั้งไม่สะอาดเท่าที่ควร แต่ก็ใช้ได้


12
คำแนะนำ: อย่าใส่เครื่องหมายขีดล่างหรือคล้ายกันในโครงร่างของคุณ
fabb

4
และคืนค่าเมื่อคุณควรจะ :)
Pizzaiola Gorgonzola

2
คำแนะนำอื่น: โหลด URL ใน iFrame เพื่อป้องกันการกะพริบ
iCaramba

@Folleto สวัสดีคุณบอกว่า "แต่ฉันไม่ได้ใช้สิ่งเหล่านี้ในโครงการใหญ่ ๆ " ทำไมไม่ ?? คุณพบปัญหาหลายอย่างในไลบรารีเหล่านี้หรือไม่ ??? ขอบคุณล่วงหน้า.
JERC

1
ไม่มีปัญหา ฉันไม่ได้ใช้มันดังนั้นฉันจึงไม่สามารถแสดงความคิดเห็นเกี่ยวกับการใช้มันในโครงการขนาดใหญ่ได้ ฉันอยากจะชี้แจงว่า :)
Folletto

57

ไม่แนะนำวิธีที่แนะนำในการเรียกวัตถุประสงค์ c จาก JS ในคำตอบที่ยอมรับ ตัวอย่างหนึ่งของปัญหา: หากคุณทำการโทรติดต่อกันสองครั้งในทันทีรายการหนึ่งถูกละเว้น (คุณไม่สามารถเปลี่ยนตำแหน่งเร็วเกินไป)

ฉันขอแนะนำแนวทางอื่นต่อไปนี้:

function execute(url) 
{
  var iframe = document.createElement("IFRAME");
  iframe.setAttribute("src", url);
  document.documentElement.appendChild(iframe);
  iframe.parentNode.removeChild(iframe);
  iframe = null;
}

คุณเรียกใช้executeฟังก์ชันซ้ำ ๆ และเนื่องจากการเรียกแต่ละครั้งดำเนินการใน iframe ของตัวเองจึงไม่ควรละเลยเมื่อเรียกอย่างรวดเร็ว

ให้เครดิตกับผู้ชายคนนี้


1
คุณยังสามารถใช้คิวใน JavaScript ที่ iOS สามารถดึงข้อมูลเพื่อหลีกเลี่ยงการสูญเสียข้อความจาก. src ที่เปลี่ยนแปลงเร็วเกินไป การใช้งานที่เชื่อมโยงข้างต้นgithub.com/marcuswestin/WebViewJavascriptBridgeใช้แนวทางคิว
kevintcoughlin

12

อัปเดต:สิ่งนี้มีการเปลี่ยนแปลงใน iOS 8 คำตอบของฉันใช้กับเวอร์ชันก่อนหน้า

อีกทางเลือกหนึ่งที่อาจทำให้คุณถูกปฏิเสธจาก App Store คือการใช้ WebScriptObject

API เหล่านี้เป็นแบบสาธารณะบน OSX แต่ไม่ได้อยู่บน iOS

คุณต้องกำหนดอินเตอร์เฟสให้กับคลาสภายใน

@interface WebScriptObject: NSObject
@end

@interface WebView
- (WebScriptObject *)windowScriptObject;
@end

@interface UIWebDocumentView: UIView
- (WebView *)webView;
@end

คุณต้องกำหนดวัตถุของคุณที่จะใช้เป็น WebScriptObject ของคุณ

@interface WebScriptBridge: NSObject
- (void)someEvent: (uint64_t)foo :(NSString *)bar;
- (void)testfoo;
+ (BOOL)isKeyExcludedFromWebScript:(const char *)name;
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector;
+ (WebScriptBridge*)getWebScriptBridge;
@end

static WebScriptBridge *gWebScriptBridge = nil;

@implementation WebScriptBridge
- (void)someEvent: (uint64_t)foo :(NSString *)bar
{
    NSLog(bar);
}

-(void)testfoo {
    NSLog(@"testfoo!");
}

+ (BOOL)isKeyExcludedFromWebScript:(const char *)name;
{
    return NO;
}

+ (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector;
{
    return NO;
}

+ (NSString *)webScriptNameForSelector:(SEL)sel
{
    // Naming rules can be found at: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/WebKit/Protocols/WebScripting_Protocol/Reference/Reference.html
    if (sel == @selector(testfoo)) return @"testfoo";
    if (sel == @selector(someEvent::)) return @"someEvent";

    return nil;
}
+ (WebScriptBridge*)getWebScriptBridge {
    if (gWebScriptBridge == nil)
        gWebScriptBridge = [WebScriptBridge new];

    return gWebScriptBridge;
}
@end

ตั้งค่าอินสแตนซ์นั้นเป็น UIWebView ของคุณแล้ว

if ([uiWebView.subviews count] > 0) {
    UIView *scrollView = uiWebView.subviews[0];

    for (UIView *childView in scrollView.subviews) {
        if ([childView isKindOfClass:[UIWebDocumentView class]]) {
            UIWebDocumentView *documentView = (UIWebDocumentView *)childView;
            WebScriptObject *wso = documentView.webView.windowScriptObject;

            [wso setValue:[WebScriptBridge getWebScriptBridge] forKey:@"yourBridge"];
        }
    }
}

ตอนนี้ภายในจาวาสคริปต์ของคุณคุณสามารถโทร:

yourBridge.someEvent(100, "hello");
yourBridge.testfoo();

แอปของฉันถูกปฏิเสธใน App Store ปัญหาที่ฉันได้รับคือ "แอปมีหรือสืบทอดจากคลาสที่ไม่ใช่แบบสาธารณะใน AppName: UIWebDocumentView"
chandru

5

ใน iOS8 คุณสามารถดูที่ WKWebView แทน UIWebView ซึ่งมีคลาสดังต่อไปนี้: WKScriptMessageHandler: จัดเตรียมวิธีการรับข้อความจาก JavaScript ที่ทำงานในเว็บเพจ


แน่นอน แต่วิธีการนี้ไม่ได้ดำเนินการอย่างต่อเนื่อง .. มันเป็นข้อผิดพลาดในกรอบซึ่งให้บริการโดย apple
Josh Kethepalli

WKWebViewมีปัญหามากมายรวมถึงการจัดการคุกกี้ไม่ถูกต้อง คำเตือนสำหรับใครก็ตามที่มาที่นี่โดยคิดว่ามันจะช่วยแก้ปัญหาของคุณได้ - Google สักหน่อยก่อนที่จะนำมาใช้
CupawnTae

3

สิ่งนี้เป็นไปได้ด้วย iOS7 ชำระเงินhttp://blog.bignerdranch.com/3784-javascriptcore-and-ios-7/


3
ไม่จริง: JavaScriptCore ไม่โต้ตอบกับ UIWebViews เป็นเครื่องมือที่ยอดเยี่ยม แต่สำหรับปัญหาที่แตกต่างกัน ;)
Folletto

1
ใช่: JSContext * context = [_webView valueForKeyPath: @ "documentView.webView.mainFrame.javaScriptContext"]; แต่ JavaScriptCore มีข้อบกพร่องมากเกินไปในขณะนี้ที่จะใช้งานจริงได้
malhal

0

ทางออกที่ดีที่สุดของคุณคือข้อเสนอของ Appcelerators Titanium พวกเขาได้สร้างสะพานจาวาสคริปต์ Obj-C แล้วโดยใช้เอ็นจิ้น JavascriptCore ที่ใช้โดย webkit นอกจากนี้ยังเป็นโอเพ่นซอร์สดังนั้นคุณจะสามารถดาวน์โหลดและคนจรจัดด้วย Obj-C ได้ตามที่คุณต้องการ


@ โฮวาไม่ใช่สะพาน Obj-C V8 V8 ใช้งานได้กับ Android เท่านั้น
รอส

0

ดูโครงการ KirinJS: Kirin JSซึ่งอนุญาตให้ใช้ Javascript สำหรับตรรกะของแอปพลิเคชันและ UI ดั้งเดิมที่เพียงพอกับแพลตฟอร์มที่ทำงานอยู่


ยังมีใครสนับสนุน kirin บ้างไหม? ฉันไม่เห็นกิจกรรมใด ๆ ในช่วงสองสามเดือนที่ผ่านมา ...
แซม

0

ฉันสร้างไลบรารีเช่น WebViewJavascriptBridge แต่มันเหมือน JQuery มากกว่าตั้งค่าง่ายกว่าและใช้งานง่ายกว่า ไม่ได้พึ่งพา jQuery (แม้ว่าจะเป็นเครดิต แต่ฉันรู้จัก WebViewJavascriptBridge ก่อนที่จะเขียนสิ่งนี้ฉันอาจจะอดกลั้นเล็กน้อยก่อนที่จะดำน้ำ) แจ้งให้เราทราบสิ่งที่คุณคิด! จ็อกกี้


0

หากคุณใช้ WKWebView บน iOS 8 ลองดูXWebViewซึ่งสามารถแสดงอินเทอร์เฟซดั้งเดิมกับจาวาสคริปต์โดยอัตโนมัติ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.