티스토리 뷰

이전까지 우리는 JavaScript에서 호출되는 function들을 사용하기 위해서 shouldStartLoadWithRequest에서 스키마로 값을 비교하여 모든것을 처리하여 주었다.

이러한 모습은 웹개발시에도 소스가 분리되는 안좋은 상태를 보여주게 되어서 좋지가 않다.

iOS 7부터는 이러한 부분을 애플에서 제공을 하여 주어서 안드로이드처럼? 만들수 있게 되었다.

안드로이드 개발 부분은 따로 설명하지 않겠다. 그만큼 편하게 개발을 할수 있게 되었다는것만 인지하면 될것 같다.


시작


xcode 9.0 / swift 3.x / min OS 7.0
JavaScriptCore를 import하여야 하고, JSExport, JSContext를 이용한다.


구현


기존에는 스키마를 파싱하여서 개발을 하면서, 파싱하는 모듈을 만들고 하는 수고를 더 들여야 하였는데, 이런 수고를 하지 않아도 된다.
구현 방법은 두가지가 있으며, 두가지중에 자신에게 맞는 방법을 골라서 하면 될것 같다.

1. 예제 html 파일

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
<!DOCTYPE html>
<html>
    
<head>
    <title>JavaScript Test</title>
    <style type="text/css">
        * {
            font-size: 40px;
        }
    </style>
    <script type="text/javascript">
        function showAlert() {
            JSModel.showAlert('Title''Javascript -> Swift Call Success.');
        }
        function getName() {
            var name = JSModel.getName();
            document.getElementById('html_label').innerHTML = 'Name is ' + name;
        }
        function jsCall() {
            JSModel.jsCall();
        }
        function jsReturn(argument) {
            document.getElementById('html_label').innerHTML = argument;
        }
    </script>
</head>
<body>
    <div style="margin-top: 10px">
        <button onclick="showAlert();">Show Alert</button></br>
        <button onclick="getName();">Get Name</button></br>
        <button onclick="jsCall();">JS Call</button></br>
        <span id="html_label" style="color: black; font-size: 50px;"></span>
    </div>
</body>
</html>
cs

특별한 내용은 없고, 세가지 예제를 사용하기 위해서 조금 간단하게 짜봤다.
첫번째. JS -> swift로 text전달
두번째. swift -> JS로 text전달
세번째. swift -> JS의 function 호출

웬만하면 세가지 케이스에서 우리의 개발은 모두 될것으로 생각된다.

2. JSExport를 이용하는 방법

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
import UIKit
import JavaScriptCore
 
// MARK: JavaScript Interface
 
@objc protocol JSInterface: JSExport {
    func showAlert(_ title: String, _ message: String)
    func getName() -> String
    func jsCall()
}
 
// MARK: ViewController
 
class ViewController: UIViewController {
    @IBOutlet weak var webView: UIWebView!
    var context: JSContext?
 
    override func viewDidLoad() {
        super.viewDidLoad()
 
        if let url = Bundle.main.url(forResource: "TestPage", withExtension: "html") {
            webView.loadRequest(URLRequest(url: url))
        }
    }
}
 
// MARK: ViewController - UIWebViewDelegate
 
extension ViewController: UIWebViewDelegate {
    func webViewDidFinishLoad(_ webView: UIWebView) {
        let key = "documentView.webView.mainFrame.javaScriptContext"
        context = webView.value(forKeyPath: key) as? JSContext
        context?.setObject(self, forKeyedSubscript: NSString.init(string: "JSModel"))
        context?.exceptionHandler = { (context, exception) in
            guard let exception = exception else {
                return
            }
            
            print("exception \(exception.description)")
        }
    }
}
 
// MARK: ViewController - JSInterface
 
extension ViewController: JSInterface {
    func showAlert(_ title: String, _ message: String) {
        let queue = DispatchQueue(label: "Alert", qos: .userInteractive)
        queue.async { [weak selfin
            guard let `self= self else {
                return
            }
            
            let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "확인", style: .default, handler: nil))
            self.present(alert, animated: true, completion: nil)
        }
    }
    
    func getName() -> String {
        return "DH"
    }
    
    func jsCall() {
        let jsReturnFunc = context?.objectForKeyedSubscript("jsReturn")
        _ = jsReturnFunc?.call(withArguments: ["JS call success!!!"])
    }
}
 
cs

간단하게 설명을 하자면, JSExport를 상속받은 JSInterface protocol파일을 만들고 그 안에 Javascript에서 호출할 function 이름을 정의한다.
이후에 protocol을 구현하여 둔 부분을 가지고 구현을 하여 주면 끝나는것이다. 핵심 부분은 webViewDidFinishLoad되는순간 Context를 교체하면서 JS의 call을 받을수 있도록 하는 것이다.

3. block으로 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func webViewDidFinishLoad(_ webView: UIWebView) {
    let key = "documentView.webView.mainFrame.javaScriptContext"
    context = webView.value(forKeyPath: key) as? JSContext
    
    let block: @convention(block)(AnyObject) -> () = { message in
        print("message = \(message)")
    }
    
    context?.setObject(unsafeBitCast(block, to: AnyObject.self),
                       forKeyedSubscript: "block" as NSString)
    
    context?.exceptionHandler = { (context, exception) in
        guard let exception = exception else {
            return
        }
        
        print("exception \(exception.description)")
    }
}
cs

수정하면 되는 부분만 따로 적도록 하였다.
위의 방식과 다른점을 따진다면 @convention이라는 키워드가 사용이 되었고, unsafeBitCast를 사용하였다.
특별히 설명할 코드는 아닌것 같아서 설명은 하지 않도록 한다.
추가적으로 html 파일은 다음과 같아졌으니 참고 바란다.

1
2
3
function showAlert() {
    block('This message is Testing');
}
cs


시간이 늦어서 잠이 오는 관계로 이정도는 붙일수 있을것이라 생각한다.
혹시라도 모르겠으면 댓글 남겨주시면 설명드림...


내일에는 최대한 빠른 시일내에 WKWebView를 이용한 JavaScript방식을 정리해보도록 하겠다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
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
글 보관함