ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [SwiftUI] TabView와 UIViewController 같이 사용해보기
    프로그래밍 2020. 2. 20. 10:23

    SwiftUI로 최근 앱을 만드는 동안 어려움은 있었지만, 빠르고 쉽게 UI를 만들고 테스트 할 수 있어서 좋았다. 여전히 잘 모르는 것들이 많이 있지만, Flutter와 함께 훌륭하다고 생각된다.

    시간도 없고 아는 것도 없어서 어려웠는데, 특히 TabView의 한 Tab에 UIViewController를 붙이는 과정에서 이해해야 할 것들이 많았다.

    앱의 기본 화면을 탭뷰로 구성했는데, 이런 식이다.

    ContentView

    struct ContentView: View {
        @State private var selection = 0
        var body: some View {
            TabView(selection: $selection) {
                BusView().tabItem {
                    VStack {
                        Text("Tab1")
                    }}.tag(0)
    
                ShopView().tabItem {
                    VStack {
                        Text("Tab2")
                    }}.tag(1)
    
                SettingView().tabItem {
                    VStack {
                        Text("Tab3")
                    }}.tag(2)
            }
        }
    }

    여기에서 ShopView를 웹뷰로 구성하고자 했다. 웹뷰가 SwiftUI에 없는 관계로 UIViewControllerRepresentable로 WebView를 만들어서 작업했다.

    AppWebView

    struct AppWebView: UIViewControllerRepresentable {
        var initialUrl: URL
        func makeUIViewController(context: Context) -> CommonWebVc {
            let vc = CommonWebVc(for: initialUrl)
            return vc
        }
    
        func updateUIViewController(
            _ vc: CommonWebVc,
            context: UIViewControllerRepresentableContext<AppWebView>) {
        }
    
        func makeCoordinator() -> Coordinator {
            Coordinator(self)
        }
    
        class Coordinator: NSObject {
            var parent: AppWebView
            init(_ view: AppWebView) {
                self.parent = view
            }
        }
    }

    CommonWebVcWKWebView를 담고 있는 UIViewController이다.
    이렇게 해서 웹뷰는 화면에 잘 표시되었지만, 탭을 왔다갔다하면 웹뷰의 이전 상태를 보전하지 못하는 문제가 생겼다. 이유는 탭에 보여질 때마다 CommonWebVc의 객체가 새로 만들어지기 때문이었다.

    SwiftUI에서 @Status, @Published, @Binding 등에 값이 변할 경우, body가 새로 그려지게 된다. View 가 sturct 구조체임을 알고 보면, 탭의 상태가 변경될 때마다 ShopView가 다시 만들어지고 있음을 짐작할 수 있다.

    결국, class인 CommonWebVc를 객체로 한번만 생성하도록 앱 환경에서 관리되어야 했다. Application 소스 어딘가에 메인 화면에서만 쓸 수 있는 CommonWebVc를 만들어 놓고 사용해도 되지만, CommonWebVc를 조금 손쉽게 재사용하기 위해 간단한 저장소를 만들어 봤다.

    CommonWebVcStore

    class CommonWebVcStore {
        private static var store: [CommonWebVc] = []
        // 해당 주소로 시작하는 Vc를 반환하거나 생성해준다. 
        static func getInstance(for url: URL) -> CommonWebVc {
            if let ins = store.first(where: { $0.initialUrl == url }) {
                return ins
            }
            let ins = CommonWebVc(url: url)
            store.append(ins)
            return ins
        }
        // 해당 Vc를 저장소에서 제거
        static func remove(_ vc: CommonWebVc) {
            store.removeAll { $0 === vc }
        }
    }

    이렇게 작성한 CommonWebVcStoreremove함수를 올바르게 호출함으로써 계속 쌓이는 문제도 해결해야 한다.
    다행히 UIViewControllerRepresentable에서 UIViewController를 제거할 때 호출되는 함수가 하나 있다.

    static func dismantleUIViewController(_ uiViewController: CommonWebVc, coordinator: AppWebView.Coordinator)

    AppWebView 변경

    AppWebView를 조금 수정해본다. AppWebView가 소멸되어도 대표하는 뷰 컨트롤러가 제거되지 않도록 속성을 하나 추가했다.

    // 시작 URL (웹뷰 생성에 필요한 Key)
    var initialUrl: URL
    // 소멸시킬 수 있는지 여부
    let isDismantlable: Bool
    
    static func dismantleUIViewController(_ uiViewController: CommonWebVc, coordinator: AppWebView.Coordinator) {
        if coordinator.parent.isDismantlable {
            print(" ---- DISMANTLE ---->>>>>>>>>> ")
            CommonWebVcStore.remove(uiViewController) // <-- 스토어에서 제거
        }
    }
    
    func makeUIViewController(context: Context) -> CommonWebVc {
        let vc = CommonWebVcStore.getInstance(for: initialUrl) // <-- 스토어를 통해 생성
        vc.delegate = context.coordinator
        return vc
    }

    ShopView 완성!!

    struct ShopView: View {
        var body: some View {
            AppWebView(initialUrl: URL(string: "https://6009.co.kr")!,
                       isDismantlable: false)
        }
    }

    정리하자면, UIKit을 SwiftUI와 사용할 때 class의 객체 생성에 주의해야 한다는 점이다.

    광고지만, 이렇게 만든 공항버스 앱이 잘 되길 바란다ㅎ

    댓글 0

Designed by Tistory.