꿈돌이랜드

[SwiftUI] SwiftUI와 UIKit와의 호환 본문

Programming/SwiftUI

[SwiftUI] SwiftUI와 UIKit와의 호환

loinsir 2023. 9. 11. 00:17
반응형

SwiftUI는 UIKit와 다음과 같은 방법들로 호환시킬 수 있습니다.

UIHostingController

A UIKit view controller that manages a SwiftUI view hierarchy.

  • SwiftUI 뷰 계층을 관리하는 UIKit의 ViewController
  • SwiftUI 뷰 → UIKit 뷰 !

정의

class UIHostingController<Content> where Content : View

SwiftUI 뷰들을 UIKit 뷰 계층에 통합하고 싶을 때, UIHostingViewController 객체를 생성합니다. 생성시간에, 이 ViewController의 rootView로 사용하고 싶은 SwiftUI 뷰를 지정해야 합니다. 나중에 SwiftUI/UIHostingController/rootView 프로퍼티를 사용해 바꿀수 있습니다. 다른 ViewController와 마찬가지로, 인터페이스에 표시하는 일반적인 ViewController로 사용하거나, 자식 ViewController로서 포함하여 사용합니다.

현재는 Xcode에서 SwiftUI 프로젝트를 열면 AppDelegate, SceneDelegate 파일들은 없어지고 [프로젝트명]App 파일이 그 역할을 대신하지만 이전의 SceneDelegate 파일은 다음과 같이 생성됩니다.

SceneDelegate.swift

import UIKit
import SwiftUI

final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        let contentView = ContentView()
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: contentView)
            self.window = window
            window.makeKeyAndVisible()
        }
    }
}

주목할 곳은 window.rootViewController에 UIHostingContoller를 할당하는 부분인데 이는 contentView가 SwiftUI View 이므로, 이를 UIKit의 ViewController로 바꾸어 ViewController 타입인 window.rootViewController 프로퍼티에 할당할 수 있게 됩니다.

UIViewRepresentable

A wrapper for a UIKit view that you use to integrate that view into your SwiftUI view hierarchy.

  • UIKit 뷰를 SwiftUI 뷰 계층에 통합하기 위한 래퍼이다.
  • Uikit뷰 → SwiftUI뷰

정의

protocol UIViewRepresentable : View where Self.Body == Never

UIViewRepresentable 인스턴스를 사용하여 UIView 객체를 SwiftUI 인터페이스에서 생성, 관리하는데 사용하세요. 이 프로토콜을 커스텀 인스턴스에 채택하고, 뷰를 생성, 업데이트, 삭제하는데 그것의 메서드들을 사용하십시오. 생성과 업데이트 프로세스들은 SwiftUI 뷰의 동작과 유사하며 이를 사용하여 앱의 현재 상태 정보로 뷰를 구성합니다. 삭제 프로세스를 사용하여 SwiftUI에서 뷰를 제거하십시오. 예를 들어, 삭제 프로세스를 사용하여 뷰가 사라짐을 다른 객체에 알릴 수 있습니다.

SwiftUI 인터페이스에 뷰를 추가하려면 인스턴스를 생성하고 이를 SwiftUI 인터페이스에 추가하십시오. 시스템은 적절한 시간에 표시가 가능한 인스턴스의 메서드를 호출하여 View를 생성하고 업데이트합니다. 다음 예는 View 계층 구조에 커스텀 정의 구조를 포함하는 것을 보여줍니다.

struct ContentView: View {
    var body: some View {
        VStack {
            Text("Global Sales")
            MyRepresentedCustomView()
        }
    }
}

시스템은 View 내에서 발생하는 변경 사항을 SwiftUI 인터페이스의 다른 부분에 자동으로 전달하지 않습니다. View가 다른 SwiftUI View와 조정되도록 하려면 이러한 상호 작용을 용이하게 만드는 Coordinator 라는 인스턴스를 제공해야 합니다. 예를 들면, 이를 사용하여 target-action을 전달하고 View에서 모든 SwiftUI View로 메시지를 위임시킵니다.

글쓴이가 UIViewRepresentable을 이용하게 된 때가 바로 SwiftUI에서 WKWebView를 사용할 때 입니다. 현재 WKWebView는 SwiftUI를 아직 지원하지 않아 SwiftUI 프로젝트에서 직접적으로 사용할 수 없습니다. 다음과 같이 구현하면 WebView를 사용할 수 있게 됩니다.

import SwiftUI
import WebKit

struct WebBrowserView: UIViewRepresentable {
    var urlString: String

    func makeUIView(context: Context) -> WKWebView {
        guard let url = URL(string: urlString) else {
            return WKWebView()
        }

        let webView: WKWebView = {
            let webConfiguration = WKWebViewConfiguration()
            let preference = WKPreferences()
            preference.javaScriptCanOpenWindowsAutomatically = true
            webConfiguration.preferences = preference
            webConfiguration.userContentController.add(context.coordinator, name: "Interface")
            let wv = WKWebView(frame: .zero, configuration: webConfiguration)
            wv.configuration.defaultWebpagePreferences.allowsContentJavaScript = true
            wv.uiDelegate = context.coordinator
            wv.navigationDelegate = context.coordinator
            return wv
        }()

        webView.load(URLRequest(url: url))
        return webView
    }

    func updateUIView(_ uiView: WKWebView, context: Context) {

    }

    func makeCoordinator() -> KurateDefaultBrowser.Coordinator {
        Coordinator(self)
    }

    class Coordinator: NSObject, WKNavigationDelegate {
        let parent: WebBrowserView

        init(_ parent: WebBrowserView) {
            self.parent = parent
        }

        func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy {
            guard let url = navigationAction.request.url,
                  let headers = navigationAction.request.allHTTPHeaderFields else {

                return .allow
            }

            if url.description.lowercased().contains("naver.com") {
                return .cancel
            }

            return .allow
        }
    }
}

extension WebBrowserView.Coordinator: WKUIDelegate {

}

extension WebBrowserView.Coordinator: WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {

    }
}

UIViewRepresentable 프로토콜은 다음 메서드를 반드시 구현해야 합니다

func makeUIView(context: Context) -> (통합하고 싶은 UIKit의 타입) // 뷰를 생성할 때 호출
func updateUIView(_ uiView: WKWebView, context: Context) // 뷰가 업데이트 될 때 호출

Coordinator는 UIKit뷰가 변화하거나 다른 SwiftUI와의 상호작용이 필요할때 구현하여 사용할 수 있습니다. 예를들어 위 예제에서는 Coordinator 객체가 WebView의 Delegate를 채택하여 메서드들을 구현하는데 사용했습니다. 이를 위해서는 Coordinator 클래스를 구현하고, func makeCoordinator() 메서드를 구현하고Coordinator 객체를 생성하여 반환시켜야 합니다.


Uploaded by N2T

반응형

'Programming > SwiftUI' 카테고리의 다른 글

swift-dependencies: Dependency lifetimes  (0) 2024.06.13