project/개발 업무

[iOS] WebView javaScript 함수 호출

lgvv 2021. 10. 25. 15:35

[iOS] WebView javaScript 함수 호출

 
앱 개발을 하면서 웹뷰를 활용하는 경우가 많음.
네이티브의 장점도 있지만, 웹뷰가 가진 장점도 있어서 각 특성을 구분하여 성격에 맞게 조합해서 사용하는 것이 좋음
 


히스토리

  • 2021.10.25 15:35
    • 초기 포스팅 작성
  • 2024.11.17 14:49
    • iOS 네이티브 환경 코드 최신화
    • 가독성 개선

 

목차

  • 웹 환경 설정
  • iOS 환경 설정

 

웹 환경 설정

웹 개발자가 이미 존재한다면 생략해도 되는데, 웹 개발자가 없을 경우 테스트 환경 구축을 위해 네이티브 개발자를 위한 가이드라인.
웹 이미 있다면 건너뛰기

  • VSCode 설치
  • node.js 설치

https://nodejs.org/ko/

Node.js

Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.

nodejs.org

 

  • 위의 사이트에 접속하면 아래 이미지 처럼 사이트가 나오는데, iOS 개발자는 주로 맥북을 사용할 것이므로 macOS 가이드라인에 따라 설치
  • 안정적이고, 신뢰도 높은거 선택했으나, 최신해도 상관 없음
    • 개인적으로는 예전에 jdk 버전을 올리면서 돌아가는 문제가 생겼던 적이 있어서, 무조건 최신을 따라가기 보다 안정적인거 선택하는거 좋아함.
LTS 환경설정
  • 설치가 잘 되었는지 확인하고 터미널로 작업하기
    • VSCode를 열어서 상단 메뉴바에 새 터미널 클릭
상단바에 새 터미널 클릭
새 터미널 클릭시 하단에 나타나는 화면
  • VSCode 내부의 터미널에 아래 커맨드 입력하면 정상적으로 설치되었다는 v~ 가 나옴

 

node --version
npm --version (node.js를 설치하면서 함께 설치된다.)

 
 

정상적으로 설치된 결과

 
 
정상적으로 설치된 것을 확인했다면 터미널에 다음과 아래의 코드를 작성

// 명령어
npm install -g nodemon

// 권한 관련 오류가 날 경우
npm install -g express-generator
sudo npm install -g express-generator

 

  • 위 작업 수행 후 폴더를 열어서 내가 원하는 프로젝트 파일명을 작성하고 프로젝트 열기

 

폴더명은 마음대로 하기~!

 
폴더를 만들었다면 터미널의 시작 위치가 자기가 만든 폴더명의 위치로 바뀜, 우리가 아까 설치한 express 패키지를 사용하여 터미널
만약 터미널이 닫혀있다면 새 터미널 열어주기
 
express --ejs 폴더명
 
(예시) 만약 내가 폴더 열기를 통해 폴더명을 TestExpress로 만들었다면
express --ejs TestExpress 를 터미널에 입력해준다.
 
이 과정이 끝나면 아래 이미지처럼 파일이 생김

똭!

 
views - index.ejs 파일을 일단 보자.
 

  • index.ejs에 작성해줄 코드
<!DOCTYPE html>
<html>
  <head>
    <!-- <title><%= title %></title> -->
    <link rel='stylesheet' href='/stylesheets/style.css' />
    


  <script type="text/javascript" charset="UTF-8">
    
    function callNative() {
       try {
          webkit.messageHandlers.callbackHandler.postMessage("MessageBody");
          print("callNative");
       } catch(err) {
           alert(err);
       }
    }


    function redHeader() {
       alert('redHeader() CALL');
       document.querySelector('h1').style.color = "red";
    }

    function funcName() {
       return "OK";
    }
  </script>
  </head>
  <body>
       
    <h1>IOS용 메시지</h1>
    <br><input type="button" onclick= 'callNative()' value="네이티브 함수 호출"/>
    <h1><%= title %></h1>
    <p>Welcome to <%= title %></p>
    
  </body>
</html>

 

  • 웹에서 보기 위해 실행하는 과정 

터미널에 둘중 하나 입력

nodemon ./bin/www
또는 
nodemon start

 
 
그 이후에 크롬 브라우저에서 
http://localhost:3000/ 입력해주기.
 

웹에 나타난 결과

 

위처럼 사이트가 나타나면 성공
 

iOS 환경설정

여기 과정부터는 Xcode에서 Swift 언어로 진행

  • UI 확인을 위한 웹뷰 세팅
    • 해당 예제에서는 스토리보드로 했는데, SwiftUI나 코드기반 UI 사용해도 무방함.
    • 웹뷰를 넣고 레이아웃 잡아주기

 

스토리보드

 

  • ViewController 코드 

import UIKit
import WebKit

final class ViewController: UIViewController {
    /// 웹의 js랑 통신할 컨텐트 컨트롤러
    let contentController = WKUserContentController()
    /// 스토리보드 기반 웹뷰에서 사용 불가능.
    let configuration = WKWebViewConfiguration()
    
    private func loadWebView() {
        guard let url = URL(string: "http://localhost:3000") else { return }
        let urlReuqest = URLRequest(url: url)
        
        // !!!: - native -> webview(js) call 초기 세팅시에 가능한 환경설정으로 source부분에 함수 대신 HTML직접 삽입 가능
        let userScript = WKUserScript(
            source: "redHeader()",
            injectionTime: .atDocumentEnd,
            forMainFrameOnly: true
        )
        contentController.addUserScript(userScript)
        
        // !!!: - webview(js) -> native call으로 name의 값을 지정하여, js에서 webkit.messageHandlers.NAME.postMessage("");와 연동되는 것, userContentController함수에서 처리
        contentController.add(self, name: "callbackHandler")
        
        /// 컨텐츠 컨트롤러 세팅
        configuration.userContentController = contentController
        
        // 웹뷰 로드
        webView.load(urlReuqest)
    }
    
    // MARK: - Initialize
    
    init() {
        super.init(nibName: nil, bundle: nil)
        configureUI()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        configureUI()
    }
    
    // MARK: - UIComponents
    
    /// 웹뷰 객체
    private lazy var webView: WKWebView = {
        $0.uiDelegate = self
        $0.navigationDelegate = self
        return $0
    }(WKWebView())
    
    /// 웹뷰가 로드될 때 필요한 인디케이터
    private let activityIndicator: UIActivityIndicatorView = UIActivityIndicatorView()

    private func configureUI() {
        view.addSubview(webView)
        webView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            webView.topAnchor.constraint(equalTo: view.topAnchor),
            webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            webView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
        ])
        
        view.addSubview(activityIndicator)
        activityIndicator.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
    
}

extension ViewController: WKNavigationDelegate,
                          WKUIDelegate,
                          WKScriptMessageHandler {
    
    /// 웹뷰가 로드가 끝난 시점에 호출
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        // !!!: - native -> webview(js) call을 통해 page에 js를 주입하는 방법
        webView.evaluateJavaScript("javascript:redHeader()") { message, error in
            print(message, error?.localizedDescription)
        }
    }
    
    /// 웹뷰에서 이벤트 떨어지면 여기로 옴
    ///
    /// 메시지에서 name으로 판단
    /// name의 경우 `enum`을 활용해 규칙을 정하는 것이 좋음
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        switch message.name {
        case "callbackHandler":
            print(message.body)
            showsAlert()
            
        default:
            print("정의되지 않은 메시지")
        }
    }
    
    private func showsAlert() {
        let alert = UIAlertController(title: "Alert", message: "Hello, World!", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
        present(alert, animated: true, completion: nil)
    }
}

 
뷰 컨트롤러에 대한 전체 코드.

  • 아주 간단한 기능들만 담았는데, 해당 원리를 기반으로 자세한 부분을 따로 확인해보면 좋음

 
결과물 

결과물

 

  • 결과물을 보면 redHeader() call의 호출되고, iOS용 메시지가 빨간색 글씨로 바뀐 것을 확인할 수 있다.

 
 
 
 
(참고)
https://zetal.tistory.com/entry/WKUserContentController

WKUserContentController 뽀개기

WKUserContentController 뽀개기 이번 포스팅에서는 WKWebView 기능 중에 하나인 WKUserContentController 기능을 살펴보겠습니다. WKWebView기능 중에 좋은 기능이 있습니다. 기존 UIWebView에서는 지원이 안됐던..

zetal.tistory.com

https://ios-development.tistory.com/701

[iOS - swift] 2. WKWebView 사용 방법 (웹뷰, 쿠키, WKScriptMessageHandler, WKNavigationDelegate, WKUIDelegate)

1. WKWebView 개념1 (UIWebView, AJAX, XHR, 캐시, 쿠키) 2. WKWebView 사용 방법 (웹뷰, 쿠키, WKScriptMessageHandler, WKNavigationDelegate, WKUIDelegate) WKWebView를 사용하기 전 알아야할 기본 개..

ios-development.tistory.com

https://yagom.net/forums/topic/loadview%EC%99%80-viewdidload-%EC%B0%A8%EC%9D%B4%EC%97%90-%EB%8C%80%ED%95%9C-%EC%A7%88%EB%AC%B8%EC%9E%85%EB%8B%88%EB%8B%A4/

loadView와 viewDidLoad 차이에 대한 질문입니다. - 야곰닷넷

안녕하세요. Swift를 공부한지 시간이 흐르니 스토리보드와 인터페이스 빌더로 UI를 구성하는 거 보다 코드만으로 View를 그리는 […]

yagom.net