ㄱ본 글은 자바스크립트 카테고리의 "하이브리드 앱에서의 함수 실행" 글과 이어집니다. 



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 allocinit];
    // 해당 웹뷰의 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

cs



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에 해당 스크립트를

추가해준다. 이렇게하면 웹뷰가 로드되었을때 미리 삽입한 스크립트들이 자동으로 실행되게 된다. 



안드로이드 , iOS에서 네이티브로 하이브리드 앱을 만들 경우 웹(JS)와  앱(ios , android)양쪽에서 서로의 함수를 실행해야 하는 경우가 생긴다. 


본 글에는 웹 <-> 앱간 함수를 실행해야 될 경우 자바스크립트에서 준비해야할 함수를 정리해둔다.


(앱에서 자바스크립트를 실행하는 코드와 자바스크립트로 앱 함수를 실행할 경우 앱에서 선언해야 하는 앱(ios / android)의 함수는 따로 다루도록 한다.)




1. 자바스크립트에서 iOS 함수 실행


 자바스크립트에서 iOS의 함수를 실행하는 방법은 두가지이다. 이는 기존에 사용되던 웹뷰 UIWebView에 더해 iOS8부터 WKWebView가 추가되었기 때문이다. 두 함수의 자바스크립트 코드와 objective c (OR Swift)코드가 다르기 때문에 유의하여야 한다. 


1.1 UIWebView

location.href = YOUR_PROTOCOL://YOUR_METHOD_NAME?PARAM_1_NAME=PARAM_2_VALUE&PARAM_2_NAME=PARAM_2_VALUE....

 UIWebView에서는 주소를 호출하는 방식을 사용한다. http 대신 임의로 지정한 이름을 프로토콜로 사용하고 도메인이 위치하는 곳에는 함수 이름을 , 쿼리스트링으로 함수의 변수들을 넘겨준다. 이렇게 실행 후 iOS 코드 내에서는 웹뷰의 주소가 변경되는 것을 감지하는 함수내에서 각 값들을 분리하여 사용하도록 한다. 


1.2 WKWebview 

 

window.webkit.messageHandlers[YOUR_HANDLER_NAME].postMessage(PARAMS)


 WKWebView에서는 전역변수로서 자동으로 webkit이라는 변수를 생성한다. 그리고 웹 -> 앱간 통신이 필요한 경우 위의 webkit 변수를 통해 이루어진다. webkit.messageHandlers에 앱에서 임의로 결정한 값이 추가로 생성되며, 그 자식 함수로 postMessage라는 함수가 생성된다. 여기서 postMessage라는 함수를 스크립트에서 실행할 경우 자바스크립트 함수를 처리하는 WKWebView의 딜리게이트 함수에서 'PARAMS'을 dictionary형태로 넘겨 받게 된다. PARAMS는 자바스크립트 객체 형태로 넘겨주어도 무방하다. 


2. 자바스크립트에서 Android 함수 실행


window[YOUR_HANDLER_NAME][YOUR_METHOD_NAME](PARAMS)

 안드로이드에서는 임의로 설정한 HANDLER이름으로 전역 변수를 생성하고 그 아래에 함수를 선언하는 형식이 된다. 일반적으로 전역 변수로서 네임스페이스로 함수들을 묶어두는 방식과 동일하다. 





위의 기능들을 사용하는데 매번 문자열을 더하거나 확인해가면서 작업할 수는 없는 노릇이라, 웹 <-> 앱간의 통신을 담당하는 스크립트 함수들을 묶음으로 만들어두고 실제로 사용하였다. 가지고 있는 기능은 아래와 같다. 

// 0. 최초 로드시

// 반드시 HY_BRIDGE.config 함수로 ios 및 안드로이드의 핸들러 / 프리픽스를 초기화 해주어야 합니다.

HY_BRIDGE.config({

ios : { prefix : 'PREFIX' , handler : 'HANDLER' },

android : { handler : 'HANDLER' }

})


// 1.1 기기 타입 가져오기

HY_BRIDGE.tools.device() // iOS / Android / Unknown

// 1.2 iOS 확인

HY_BRDIGE.tools.isiOS() // true or false

// 1.3 Android 확인

HY_BRIDGE.tools.isAndroid() // true or false



// 2 웹-> 어플리케이션 함수

// 2.1 생성

HY_BDIRGE.app.extend({

method_1 : function(foo,bar){

HY_BDRIDGE.app.dispatch({

method : "METHOD_NAME_IN_APP",

params : { foo : 0 , bar : 1 }

});

},

method_2 : function(){

..

}

})

// 2.2 실행

HY_BRIDGE.app.call(method_1 , foo , bar);


// 2.3 특정 이벤트일때

// 2.3.1 생성

HY_BRIDGE.app.evt.insert(HY_BRIDGE_app.IDENTIFIER.ON_LOAD_FINISHED, function(){

// 함수 내용

})

// 2.3.2 (앱에서) 실행 (선언된 순서대로 실행됩니다.)

HY_BRIDGE.app.evt.flush(HY_BRIDGE.app.IDENTIFIER.ON_LOAD_FINISHED);


// 3 어플리케이션 -> 웹

// 어플리케이션에서 자바스크립트는 쉽게 실행할 수 있지만, 파편화 되는 것을 방지하기 위해 선언 및 사용부를 통일해두었다.

// 3.1.1 함수 생성

HY_BRIDGE.web.extend({

method_1 : function(foo){

console.log("Hello " + foo)

}

});

// 3.1.2 함수 실행

HY_BRIDGE.web.call('method_1' , foo)

// 3.2.1 변수 생성

HY_BRIDGE.web.declare({ value_1 : 0 , value_2 : 1 })

// 3.2.2 변수 가져오기

HY_BRIDGE.web.get('value_1')




'DEV > JavaScript' 카테고리의 다른 글

문자열 관련 함수 묶음  (0) 2017.10.17
쿠키 관리 함수 묶음  (0) 2017.10.17
주소의 쿼리스트링을 조작하는 함수 모음  (0) 2017.10.17

문자열에서 특정 문자를 가지고 있는지 확인하는 경우 사용하는 함수를 묶어두었다


문자열에서 영문자 (대소구분x) , 영 대문자 , 영 소문자 , 숫자 , 특수문자 , 한글 , 공백 , 줄바꿈 등을 묶어 두었다.

그리고 trim이 존재하지 않는 경우 직접 만들어준다. 



+ Recent posts