ㄱ본 글은 자바스크립트 카테고리의 "하이브리드 앱에서의 함수 실행" 글과 이어집니다.
Objective C 네이티브로 하이브리드 앱을 만들때에 웹에서 Objective C 함수를 실행하기 위해서 자바스크립트단에도 준비를 해야하지만, 실제로 실행될 Obj C 함수도 어플리케이션단에 준비되어있어야한다. 이때, 기존의 UIWebView와 iOS8부터 사용되고 있는 WKWebView에서의 처리가 각각 다르기에 각 클래스에 맞게 정리해둔다.
1. 자바스크립트 -> iOS
1.1. UIWebView
기존에 많이 사용되던 클래스이다. 자바스크립트 사이드에서의 구현은 간단하지만 어플리케이션 레벨에서 약간 까다로운 준비를 해야한다.
아래의 코드는 하나의 뷰 컨트롤러에서 webView:shouldStartLoadWithRequest:navigationType: 함수를 사용하여 웹뷰의 request를 가로채
필요한 값들만 뽑아낸다. 요청의 프로토콜이 기존에 약속되어 있던 코드인지 확인 한 후 host는 함수명으로 querystring은 키 / 변수 쌍으로 사용하고, querystring을 사용할때 Dictionary로 변환하여 선언해둔 함수로 넘겨준다. 넘겨받은 함수명으로 바로 함수를 사용할 경우 오류가 발생할 수 있으므로 RespondToSelector: 함수로 넘겨받은 함수가 실제로 존재하는지 확인한 후 실행하도록 했다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | #import <UIKit/UIKit.h> // UIWebView의 Delegate함수를 실행해야 하므로 Delegate를 추가해준다. @interface UIWVController: UIViewController<UIWebViewDelegate> // 사용할 웹뷰를 선언해준다. @property(nonatomic) IBOutlet UIWebView* webView; @end @implementation FrontViewController - (void)viewDidLoad { [super viewDidLoad]; // 웹뷰를 초기화 해준다. webView = [[UIWebView alloc] init]; // 해당 웹뷰의 Delegate를 현재 ViewController로 지정해준다. [webView setDelegate:self]; } #pragma mark - UIWebView Delegate Methods // 웹뷰의 리퀘스트가 시작되면 (주소가 바뀌면) 실행되는 함수 // UIWebView일때 location.href 혹은 <a href=''.../> 를 사용하면 아래 함수가 실행된다. - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{ if([request.URL.scheme rangeOfString:@"ioscall"].location != NSNotFound){ // web -> app 통신을 걸러낸다 // 이 예제에서는 ioscall이라는 프로토콜을 사용하는 것을 전제로 함 // scheme : 요청의 프로토콜명 보통은 http / https가 넘어온다. // host : 함수의 이름 // query : 파라미터 // 넘겨받은 함수를 확인 및 실행하기 위한 NSSelector 변수 SEL _selector_from_web; // 변수가 있는지 없는지 확인한다. // 변수가 존재하는 경우에는 Selector에 : 가 붙으므로 나누어 선언해준다. if(request.URL.query){ NSLog(@"변수가 있습니다. %@" , request.URL.query); _selector_from_web = NSSelectorFromString([NSString stringWithFormat:@"%@:" , request.URL.host]); } else{ NSLog(@"변수가 없습니다. %@" , request.URL.query); _selector_from_web = NSSelectorFromString(request.URL.host); } if([self respondsToSelector:_selector_from_web]){ if(request.URL.query){ // 변수가 존재하는 경우 Boolean err_occurred = false; // 넘겨받은 쿼리스트링을 &를 기준으로 나눕니다 NSArray *queries_arr = [[request.URL.query stringByRemovingPercentEncoding] componentsSeparatedByString:@"&"]; NSMutableDictionary *queries = [[NSMutableDictionary alloc]init]; for(int idx = 0 ; idx < queries_arr.count ; idx++){ if([[queries_arr objectAtIndex:idx] rangeOfString:@"="].location == NSNotFound){ // 형식이 잘못되었을 경우 err_occurred = true; break; } NSArray *separated = [[queries_arr objectAtIndex:idx] componentsSeparatedByString:@"="]; [queries setObject:[separated objectAtIndex:1] forKey:[separated objectAtIndex:0]]; } if(err_occurred){ // 에러가 생겼다. NSLog(@"올바르지 않은 형태의 쿼리입니다. %@" , request.URL.query); return NO; } else{ // 2. method call // 함수가 컨트롤러에 존재할때만 실행되도록 되어있습니다. [self performSelector:_selector_from_web withObject:queries]; } } else { // 변수가 존재하지 않는 경우 [self performSelector:_selector_from_web]; } } else{ // 넘겨받은 함수가 존재하지 않는 경우 NSLog(@"셀렉터가 존재하지 않습니다"); return NO; } } return YES; } #pragma mark - JS -> APP Methods - (void) mockup_method:(NSDictionary *)params{ // 스크립트에서 location.href = ioscall://mockup_method?value=1을 실행할 경우 // 이 함수가 실행됩니다. NSLog(@"mockup method called ! value = [%@]" , [params objectForKey:@"value"]); } @end |
1.2. WKWebView
WKWebView는 UIKit이외에 WebKit을 추가해주어야한다. 좀 더 추가해주어야 하는 요소들이 많고 상속받아야 하는 것들이 많지만 그만큼 더 세밀한
작업을 지원한다. 스토리보드에서는 WKWebView를 추가해 사용할 수 없으므로 크기를 지정해주고 직접 뷰에 추가해주어야 하는 불편함이 있다. request를 다루는 부분과 스크립트를 다루는 부분이 분리되어 있어 코드를 보기에 더 편해졌다. UIWebView와 마찬가지로 함수가 존재할때만 실행하도록 한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | #import <UIKit/UIKit.h> // WkWebView를 사용하기 위해 WebKit을 참조합니다. @import WebKit; // WkWebView에서 주소가 변경되거나 스크립트를 사용할 경우 사용할 Delegate함수를 사용하기 위해 // 아래의 Delegate들을 참조합니다 @interface MainViewController : UIViewController<WKUIDelegate , WKNavigationDelegate , WKScriptMessageHandler> @property(nonatomic) WKWebView* webView; @end @implementation MainViewController{ // 지역 변수를 선언합니다. // 웹뷰의 랜더 속도 및 각종 설정들을 담당하는 클래스입니다. WkWebViewConfiguration *config; // 자바스크립트에서 메시지를 받거나 자바스크립트를 실행하는데 필요한 클래스입니다. WKUserContentController *jsctrl; } - (void) viewDidLoad{ [super viewDidLoad]; // WkWebViewConfiguration과 WKUserContentController를 초기화해줍니다. config = [[WKWebViewConfiguration alloc]init]; jsctrl = [[WKUserContentController alloc]init]; // 자바스크립트 -> ios에 사용될 핸들러 이름을 추가해줍니다. // 본 글에서는 핸들러 및 프로토콜을 ioscall로 통일합니다. [jsctrl addScriptMessageHandler:self name:@"ioscall"]; // WkWebView의 configuration에 스크립트에 대한 설정을 정해줍니다. [config setUserContentController:jsctrl]; // 웹뷰의 딜리게이트들을 새로 초기화해줍니다. [webView setUIDelegate:self]; [webView setNavigationDelegate:self]; CGRect frame = [[UIScreen mainScreen]bounds]; // WkWebView는 IBOutlet으로 제공되지 않아 스토리보드에서 추가할 수 없습니다. // 웹뷰의 크기를 정해준 후 초기화하고 본 ViewController의 뷰에 추가합니다. webView = [[UIWKWebView alloc] initWithFrame:frame configuration:config]; [[self view]addSubView:webView]; } #pragma mark - delegate method // WKScriptMessageHandler에 의해 생성된 delegate 함수입니다. // 자바스크립트에서 ios에 wekkit핸들러를 통해 postMessage함수를 사용한 경우 실행됩니다. - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{ // message의 body에 전달받은 객체가 NSDictionary형식으로 들어있습니다. // 본 예제에서는 함수이름을 "name"으로, 파라미터 묶음을 "params"로 정합니다. if([[[message body] allKeys] count] > 0 && [[message body]objectForKey:@"name"]){ SEL _selector = nil; if([[message body] objectForKey:@"params"]){ // 변수를 가진 경우 _selector = NSSelectorFromString([NSString stringWithFormat:@"%@:" , [[message body] objectForKey:@"name"]]); if([self respondsToSelector:_selector]){ // 현재 뷰 컨트롤러에 함수가 존재하는지 확인합니다. [self performSelector:_selector withObject:[[message body]objectForKey:@"params"]]; } else{ NSLog(@"셀렉터가 존재하지 않습니다"); } } else{ _selector = NSSelectorFromString([NSString stringWithFormat:@"%@", [[message body]objectForKey:@"name"]]); if([self respondsToSelector:_selector]){ // 현재 뷰 컨트롤러에 함수가 존재하는지 확인합니다. [self performSelector:_selector]; } else{ NSLog(@"셀렉터가 존재하지 않습니다."); } } } else{ NSLog(@"잘못된 javascript 요청입니다."); } } #pragma mark - JS -> ios Method - (void) mockup_method:(NSDictionary *)params{ // 스크립트에서 webkit.messageHandlers.ioscall.postMessage 함수를 실행할 경우 // 이 함수가 실행됩니다. NSLog(@"mockup method called ! value = [%@]" , [params objectForKey:@"value"]); } @end | cs |
2. iOS -> 자바스크립트
iOS상에서 자바스크립트를 실행하는 것도 UIWebView와 WKWebView간 차이가 있다. 반대의 경우보다는 비교적 간단하다.
2.1. UIWebView
UIWebView에서는 뷰 컨트롤러 내에서 stringByEvaluatingJavascriptFromString함수를 이용해 자바스크립트를 호출한다. 다만 스크립트를 직접
문자열로 만들어 호출하는만큼 문자열을 조작하는 함수를 따로 만들어 두는 것이 편할 것이다.
1 | [webView stringByEvaluatingJavascriptFromString:@"window.alert('Hello World')"]; | cs |
2.2. WKWebView
WKWebView에서의 스크립트 호출도 UIWebView와 비슷한 방법으로 실행된다. 함수는 evaluateJavasScript:completionHandler: 이며
completionHandler에 블록함수를 넣어 자바스크립트가 실행된 뒤의 이벤트를 캐치할 수 있다.
1 2 3 | [webView evaluateJavaScript:@"window.alert('Hello World');" completionHandler:^{ NSLog(@"evaluate Completed"); }]; | cs |
즉각적으로 자바스크립트를 실행할 경우 UIWebView와 사용법이 크게 다르지 않지만, WKWebView에서는 차별화된 기능을 갖고 있는데, WKUserScript
클래스를 이용해 문서 최상단 / 최하단에 스크립트를 직접 삽입해주는 addScript: 함수가 그것이다. 사용법은 아래와 같다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | - (void) viewDidLoad{ [super viewDidLoad]; // WkWebViewConfiguration과 WKUserContentController를 초기화해줍니다. config = [[WKWebViewConfiguration alloc]init]; jsctrl = [[WKUserContentController alloc]init]; // 자바스크립트 -> ios에 사용될 핸들러 이름을 추가해줍니다. // 본 글에서는 핸들러 및 프로토콜을 ioscall로 통일합니다. [jsctrl addScriptMessageHandler:self name:@"ioscall"]; // WkWebView의 configuration에 스크립트에 대한 설정을 정해줍니다. [config setUserContentController:jsctrl]; // 스크립트를 초기화 합니다. WKUserScript *cookie_script = [[WKUserScript alloc]initWithSource:@"alert('load!')" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO]; // UserContentController에 스크립트를 삽입합니다. [jsctrl addUserScript:cookie_script]; // 웹뷰의 딜리게이트들을 새로 초기화해줍니다. [webView setUIDelegate:self]; [webView setNavigationDelegate:self]; CGRect frame = [[UIScreen mainScreen]bounds]; // WkWebView는 IBOutlet으로 제공되지 않아 스토리보드에서 추가할 수 없습니다. // 웹뷰의 크기를 정해준 후 초기화하고 본 ViewController의 뷰에 추가합니다. webView = [[UIWKWebView alloc] initWithFrame:frame configuration:config]; [[self view]addSubView:webView]; } | cs |
소스는 위의 WKWebView 사용부의 viewDidLoad 부분을 잘라낸 것인데 , ln14 ~ 18이 추가되었다. WKUserScript클래스로 문서의 최상단에 둘지
최하단에 둘지 결정하고 (injectionTime) 모든 프레임에 적용할 것인지 (forMainFrameOnly)결정한 후 WKUserContentController에 해당 스크립트를
추가해준다. 이렇게하면 웹뷰가 로드되었을때 미리 삽입한 스크립트들이 자동으로 실행되게 된다.
'DEV > iOS' 카테고리의 다른 글
ScrollView와 PageControl을 이용한 페이징 처리 (0) | 2014.05.06 |
---|---|
NSThread를 통한 데이터 처리와 화면 제어 팁 (0) | 2014.03.09 |
NSURLConnection와 NSURLRequest를 이용해 POST데이터를 동기적으로 통신 (0) | 2014.03.09 |
UIAlertView 간단한 함수로 만들어 두기 (0) | 2014.03.09 |
NSString을 파일로 읽고 쓰기 (0) | 2014.03.09 |