[WebKit] WKWebView 띄우기 (feat.Bridge)

Sean·2023년 5월 8일
0

WebKit

목록 보기
1/4

누군가에게 알려주기 보다는 나 스스로 정리 하며 언젠가 다시 사용할 때를 대비하는 글을 작성할것이다.

참고 자료: WebKit 공식문서

시작

Document: WKWebView

개요

  • WebView 구현 방법에는 3가지 방법이 존재한다.
    그러나 그 중에서 WKWebView 를 사용해서 WebView를 구현할것이다.

버전

  • iOS 8.0+
  • macOS 10.10+

번역

Overview

WKWebView 객체는 플랫폼 기본 뷰로, 앱 UI에 웹 콘텐츠를 매끄럽게 통합할 수 있도록 지원합니다. 웹 뷰는 전체적인 웹 브라우징 경험을 지원하며, HTML, CSS 및 JavaScript 콘텐츠를 앱의 네이티브 뷰와 함께 제공합니다. 앱의 레이아웃 및 스타일링 요구 사항이 네이티브 뷰보다 웹 기술로 더 쉽게 충족되는 경우에 사용할 수 있습니다. 예를 들어, 앱의 콘텐츠가 자주 변경되는 경우 사용할 수 있습니다.

웹 뷰는 델리게이트 객체를 통해 탐색 및 사용자 경험에 대한 제어를 제공합니다. 네비게이션 델리게이트를 사용하여 사용자가 웹 콘텐츠에서 링크를 클릭하거나 탐색에 영향을 미치는 방식으로 콘텐츠와 상호 작용할 때 반응할 수 있습니다. 예를 들어, 특정 조건이 충족될 때까지 사용자가 새 콘텐츠로 이동하는 것을 방지할 수 있습니다. UI 델리게이트를 사용하여 웹 콘텐츠와 상호 작용에 대한 응답으로 경고 또는 콘텍스트 메뉴와 같은 네이티브 UI 요소를 표시할 수 있습니다.

WKWebView 객체를 프로그래밍 방식으로 뷰 계층 구조에 내장하거나 인터페이스 빌더를 사용하여 추가할 수 있습니다. 인터페이스 빌더는 데이터 감지기, 미디어 재생 및 상호 작용 동작과 같은 많은 사용자 정의를 지원합니다. 더 많은 사용자 정의가 필요한 경우, WKWebViewConfiguration 객체를 사용하여 웹 뷰를 프로그래밍 방식으로 만들 수 있습니다. 예를 들어, 웹 뷰 구성 객체를 사용하여 사용자 정의 URL 스키마에 대한 핸들러를 지정하고, 쿠키를 관리하고, 웹 콘텐츠에 대한 환경 설정을 사용자 정의할 수 있습니다.

화면에 웹 뷰가 나타나기 전URLRequest 구조체를 사용하여 웹 서버에서 콘텐츠를 로드하거나 로컬 파일 또는 HTML 문자열에서 콘텐츠를 직접 로드합니다. 웹 뷰는 초기 로드 요청의 일부로 이미지나 동영상과 같은 임베디드 리소스를 자동으로 로드합니다. 그런 다음 콘텐츠를 렌더링하고 결과를 뷰의 바운드 사각형 내에 표시합니다. 다음 코드 예제는 기본 뷰를 사용자 정의 WKWebView 객체로 대체하는 뷰 컨트롤러를 보여줍니다.

import UIKit
import WebKit

class ViewController: UIViewController, WKUIDelegate {
    
    var webView: WKWebView!
    
    override func loadView() {
        let webConfiguration = WKWebViewConfiguration()
        webView = WKWebView(frame: .zero, configuration: webConfiguration)
        webView.uiDelegate = self
        view = webView
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let myURL = URL(string:"https://www.apple.com")
        let myRequest = URLRequest(url: myURL!)
        webView.load(myRequest)
    }
}

웹 뷰는 웹 콘텐츠에 나타나는 전화번호를 자동으로 전화 링크로 변환합니다. 사용자가 전화 링크를 탭하면 전화 앱이 시작되고 번호가 다이얼됩니다. 기본 데이터 감지기 동작을 변경하려면 WKWebViewConfiguration 객체를 사용하세요.

setMagnification(_:centeredAt:)를 사용하여 웹 콘텐츠의 크기를 웹 뷰에 처음 나타날 때 프로그래밍 방식으로 설정할 수 있습니다. 그 이후로는 사용자가 제스처를 사용하여 크기를 변경할 수 있습니다.


웹 콘텐츠를 통한 탐색 관리(Manage the navigation through your web content)

WKWebView는 링크, 앞으로/뒤로 버튼 등을 사용하여 다른 웹 페이지 사이를 이동할 수 있는 완전한 브라우징 경험을 제공합니다. 사용자가 콘텐츠에서 링크를 클릭하면 웹 뷰는 브라우저처럼 작동하여 해당 링크의 콘텐츠를 표시합니다. 탐색을 금지하거나 웹 뷰의 탐색 동작을 사용자 정의하려면 WKNavigationDelegate 프로토콜을 준수하는 개체인 탐색 대리자(navigation delegate)를 제공하세요. 탐색 대리자를 사용하여 웹 뷰의 탐색 동작을 수정하거나 새 콘텐츠의 로딩 진행 상황을 추적하세요.

또한 WKWebView의 메소드를 사용하여 콘텐츠를 프로그래밍 방식으로 탐색하거나 앱 인터페이스의 다른 부분에서 탐색을 트리거할 수도 있습니다. 예를 들어, UI에 앞으로/뒤로 버튼이 포함되어 있는 경우 해당 버튼을 웹 뷰의 goBack(_:)goForward(_:) 메소드에 연결하여 해당하는 웹 탐색을 트리거합니다. canGoBack 및 canGoForward 속성을 사용하여 버튼을 활성화 또는 비활성화할 시기를 결정하세요.


공유 옵션 제공(Provide sharing options)

사람들은 WKWebView의 내용을 앱이나 다른 사람들과 공유하고자 할 수 있습니다. UIActivityViewController를 사용하여 공유 시트를 제공하여 사람들이 웹 콘텐츠를 공유할 수 있는 모든 방법을 제공하세요.

만약 앱이 com.apple.developer.web-browser 권한을 가지고 있다면, iOS 공유 시트는 http 또는 https 웹 페이지에 대해 홈 화면에 추가하는 Add to Home Screen 옵션을 제공할 수 있습니다. 이를 통해 웹 앱이나 북마크에 대한 편리한 링크를 만들 수 있습니다. 현재 웹 페이지를 홈 화면에 추가하도록 허용하려면, UIActivityViewController를 생성할 때 activityItems 배열WKWebView 인스턴스를 포함시켜 init(activityItems:applicationActivities:)를 호출하세요. 브라우저 앱을 만드는 방법에 대한 자세한 정보는 Preparing your app to be the default web browser을 참조하세요.


  • 뭔가 많은 내용이 작성이 되어있는 듯 하지만 짧게 짧게 요약할 수 있다.
    1. 웹뷰는 델리게이트를 통해서 제어를 한다
    2. 사용자 정의를 하려면 WKWebViewConfiguration 객체를 사용한다.
    3. 웹 뷰가 나타나기 전에 URLRequest를 사용해 콘텐츠 로드한다.
    4. 웹 뷰의 탐색 동작 정의를 위해서 WKNavigationDelgate 프로토콜을 준수하여 delegate를 사용한다.

분석

기본적인 설정이나 설치 부분은 많은 자료가 있으니 설치는 알아서..
한줄 설명 : [프로젝트 -> TARGETS > Build Phases -> Link Binary With Libraries -> + -> WebKit.framework]

기초적인 코드

import UIKit
import WebKit

class ViewController: UIViewController, WKUIDelegate {
    
    var webView: WKWebView!
    
    override func loadView() {
        let webConfiguration = WKWebViewConfiguration()
        webView = WKWebView(frame: .zero, configuration: webConfiguration)
        webView.uiDelegate = self
        view = webView
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let myURL = URL(string:"https://www.apple.com")
        let myRequest = URLRequest(url: myURL!)
        webView.load(myRequest)
    }
}
  • 공식 문서에 있는 코드를 테스트 해봤다.

관련 Protocol

  1. WKUIDelegate

    • JavaScript, 기타 플러그인 콘텐츠 이벤트를 캐치해 동작한다.
    • 기본 사용자 인터페이스 제공
    • 웹페이지 대신 네이티브 인터페이스 요소 표시에 사용
  2. WKNavigationDelegate

    • 웹 페이지의 start, lodaing, finish, error 이벤트 캐치해 사용자 정의 동작 구현
    • 웹 뷰에서 탐색 요청 수락, 로드 및 완료하는 과정에서 트리거 되는 동작 구현
    • delegate 함수 이용해 다른 작업 처리 용도
  • 델리게이트를 활용한 코드를 따로 작성해볼 수 도 있지만 중요한건 WKWebView Bridge 를 연결하는게 더 중요하다 보기에 바로 넘어가겠다.

심화: WKWebView Bridge 연결하기

  • JS 를 Bridge로 연결하는 방법
  • 웹뷰로 웹 페이지의 이벤트를 받아서 처리를 해야 할 때 사용

1. Bridge regist

브리지 설정을 위해서는 WKWebViewConfiguration을 WKWebView 초기화 시 넣어주어야 한다.

let contentController = WKUserContentController()
contentController.add(self,name: "bridge")

let configuration = WKWebViewConfiguration()
configuration.userContentController = contentController

bridge를 사용하기 위해선 우선 WKUserContentController를 생성해 그 안에 사용할 bridge의 이름을 추가한다.
그 후에 WKWebViewConfiguration를 생성하여 userContentControllerWKUserContentController를 넣어준다.

Protocol: WKScriptMessageHandler

.add 를 할 경우 WKScriptMessageHandler 프로토콜을 준수해줘야 하며 아래의 해당 함수를 선언해줘야 한다.

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { }

2. WKWebView init

self.webView = WKWebView(frame: self.view.bounds, configuration: configuration)
self.webViewBase.addSubview(self.webView)
self.layout()

private func layout() {
	self.webView.translatesAutoresizingMaskIntoConstraints = false
	NSLayoutConstraint.init(item: self.webView, attribute: .leading, relatedBy: .equal, toItem: self.webViewBase, attribute: .leading, multiplier: 1.0, constant: 0).isActive = true
	NSLayoutConstraint.init(item: self.webView, attribute: .trailing, relatedBy: .equal, toItem: self.webViewBase, attribute: .trailing, multiplier: 1.0, constant: 0).isActive = true
	NSLayoutConstraint.init(item: self.webView, attribute: .top, relatedBy: .equal, toItem: self.webViewBase, attribute: .top, multiplier: 1.0, constant: 0).isActive = true
	NSLayoutConstraint.init(item: self.webView, attribute: .bottom, relatedBy: .equal, toItem: self.webViewBase, attribute: .bottom, multiplier: 1.0, constant: 0).isActive = true        
	view.layoutIfNeeded()
}

WKWebView 초기화 시 configuration에는 1에서 만들어둔 WKWebViewConfiguration를 넣어준다.


3. Page load

Method: load()

load 메서드를 사용하며 해당 메서드의 선언은 다음과 같다.

func load(_ request: URLRequest) -> WKNavigation?

입력 파라미터로는 URLRequest를 받는다.

Struct: URLRequest

URLRequest의 init초기화 선언은 다음과 같다.

init(
    url: URL,
    cachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy,
    timeoutInterval: TimeInterval = 60.0
)

URL 은 string 값이므로 String을 입력하면 되겠지만 조금 더 깔끔한 코드 작성을 위해서 URLComponents를 사용할 것이다.

Struct: URLComponents

URLComponets에서 base 가 될 URL을 String이라는 파라미터에 넣어준다.
URLQueryItem을 만들어준다. URL의 쿼리 부분으로 들어갈 name-value 쌍이다.
URLComponentsqueryItems에 만들어준 item들을 넣어준다. 넣어준 순서대로 URL 쿼리 문자열에 나오게 된다.

var components = URLComponents(string: "https://m.search.naver.com/search.naver?sm=mtp_hty.top&where=m&")!
components.queryItems = [URLQueryItem(name: "query", value: "Swift")]
let request = URLRequest(url: components.url!)
self.webView.load(request)

4. 동작

1에서 WKScriptMessageHandler프로토콜을 위임 받은 후에 선언해준 함수를 통해서 Bridge 가 들어오게 된다.
message.name 으로 어떠한 Bridge인지 확인하고 그에 맞는 로직을 작성한다.
파라메터는 message.body에 담겨오며 Any 타입이기에 사용하고자 하는 타입으로 타입캐스팅 후 사용하면 된다.


5. 기타 프로토콜

Protocol: WKNavigationDelegate
Protocol: WKUIDelegate

위의 두 프로토콜의 경우에는 위에서 기초적인 코드 부분에서 관련 프로토콜부분에서 이야기를 했으며 해당 프로토콜로 동작하고자 하는 함수들은 따로 문서를 보고 원하는 함수들을 사용한다.

  • 동작 스크린샷


6. 기타 추가 코드

PreferencesConfiguration 의 설정이 추가로 더 필요하다.

let preferences = WKPreferences()
let defaultPreferences = WKWebpagePreferences()
preferences.javaScriptCanOpenWindowsAutomatically = true

if #available(iOS 14.0, *) {
	defaultPreferences.allowsContentJavaScript = true
	configuration.defaultWebpagePreferences = defaultPreferences
} else {
	preferences.javaScriptEnabled = true
	configuration.preferences = preferences
}

javaScriptCanOpenWindowsAutomatically 해당 프로퍼티는 사용자의 동작 없이 자동으로 자바스크립트가 창을 열 수 있는지를 확인 하는 값이다.

javaScriptEnabledallowsContentJavaScript 는 둘 다 자바스크립트의 실행 여부를 확인하는 프로퍼티이다.
분기처리를 한 이유는 javaScriptEnabled 해당 프로퍼티가 deprecated 되기 때문이다.

velog: [Deprecated] WebKit : javaScriptEnabled

해당 preference 작업이 끝나면 configuration 의 프로퍼티에 맞게 넣어주면 된다.

그 후에는 델리게이트 사용을 위한 델리게이트 지정까지 끝내주면 WebView 구현은 끝나게 된다.

self.webView.uiDelegate = self
self.webView.navigationDelegate = self

7. 전체 코드

import UIKit
import WebKit

class WebViewController: UIViewController{

    @IBOutlet weak var webViewBase: UIView!
    
    private var webView: WKWebView = WKWebView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.webView.uiDelegate = self
        self.webView.navigationDelegate = self
        
        self.navigationItem.title = "WebView"
        
        let contentController = WKUserContentController()
        let configuration = WKWebViewConfiguration()
        contentController.add(self,name: "bridge")
        configuration.userContentController = contentController
        
        let preferences = WKPreferences()
        let defaultPreferences = WKWebpagePreferences()
        preferences.javaScriptCanOpenWindowsAutomatically = true
        if #available(iOS 14.0, *) {
            defaultPreferences.allowsContentJavaScript = true
            configuration.defaultWebpagePreferences = defaultPreferences
        } else {
            preferences.javaScriptEnabled = true
            configuration.preferences = preferences
        }
        
        self.webView = WKWebView(frame: self.view.bounds, configuration: configuration)
        self.layout()
        
        var components = URLComponents(string: "https://m.search.naver.com/search.naver?sm=mtp_hty.top&where=m&")!
        components.queryItems = [URLQueryItem(name: "query", value: "Swift")]
        let request = URLRequest(url: components.url!)
        webView.load(request)
    }
    
    private func layout() {
        self.webViewBase.addSubview(self.webView)
        self.webView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.init(item: self.webView, attribute: .leading, relatedBy: .equal, toItem: self.webViewBase, attribute: .leading, multiplier: 1.0, constant: 0).isActive = true
        NSLayoutConstraint.init(item: self.webView, attribute: .trailing, relatedBy: .equal, toItem: self.webViewBase, attribute: .trailing, multiplier: 1.0, constant: 0).isActive = true
        NSLayoutConstraint.init(item: self.webView, attribute: .top, relatedBy: .equal, toItem: self.webViewBase, attribute: .top, multiplier: 1.0, constant: 0).isActive = true
        NSLayoutConstraint.init(item: self.webView, attribute: .bottom, relatedBy: .equal, toItem: self.webViewBase, attribute: .bottom, multiplier: 1.0, constant: 0).isActive = true
        
        view.layoutIfNeeded()
    }
}

extension WebViewController: WKScriptMessageHandler, WKNavigationDelegate, WKUIDelegate {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        print(message.name)
    }
}

관련 동작을 하는 부분들을 묶어 두었으니 확인하기에는 편하나 사용에서는 변수나 함수의 위치는 새로고쳐서 코드 작성을 해야한다.

참고자료

Protocol: WKScriptMessageHandler
Protocol: WKNavigationDelegate
Protocol: WKUIDelegate

Struct: URLRequest
Struct: URLComponents

Method: load()

Document: WKWebView

velog: [Deprecated] WebKit : javaScriptEnabled

기타

당연 틀린 부분 지적은 감사하나 비난은 정중하게 사양하겠다.

  • 뭐야..번역 부분 블럭으로 감추게 했는데 왜 동작 안하지?
  • 아...뭔가 이거 velog 미리보기에서는 잘 되는데 실제 포스트 하면 동작안하는 코드가 꽤 되네 사진도 작은 사이즈인데 올리니까 엄청 커져 버리고
profile
"잘 할 수 있을까?"를 고민하기보단 재밌어 보이는건 일단 하고, 잘하기 위해 그냥 계속합니다.

0개의 댓글