SwiftUI | UIKit 과 함께 사용하기. UIViewRepresentable, UIViewControllerRepresentable, UIHostingController

드디어 실제 프로젝트에서 가장 중요한 부분을 다루게 되었네요.

이번 글에서는 SwiftUI와 UIKit을 함께 사용하는 것에 대해 다뤄보려 합니다.

  1. SwiftUI 에서 UIView 를 추가하기.
  2. SwiftUI 에서 UIViewController 를 추가하기.
  3. UIKit 에서 SwiftUI를 추가하기.


자, 3가지를 어떻게 사용하는지 보러 가보시죠 ! 고고씽 ~


1. SwiftUI 에서 UIView 를 추가하기.

✔️ UIViewRepresentable protocol을 활용해야 합니다.
UIViewRepresentable 를 따르는 struct(구조체) 을 만들어서 SwiftUI 에서 바로 사용하면 됩니다.


✔️ UIViewRepresentable protocol를 따르는 struct 는 반드시 두 가지 메소드를 구현해야합니다.
func makeUIView(context: Context) -> UIKit에서의타입
– SwiftUI 에서 나타낼 뷰를 반환시킵니다.
func updateUIView(_ uiView: UIKit에서의타입, context: Context)
– SwiftUI에서 업데이트가 발생할 때 실행됩니다.

final class RedLabel: UILabel {
    override func awakeFromNib() {
        super.awakeFromNib()

        textColor = .red
    }
}

struct RepresentableRedLabel: UIViewRepresentable {
    var text: String

    let redLabel = RedLabel()

    func makeUIView(context: Context) -> UILabel {
        redLabel
    }

    func updateUIView(_ uiView: UILabel, context: Context) {
        redLabel.text = text
    }
}

struct ContentView: View {
    var body: some View {
        RepresentableRedLabel(text: "펭하")
    }
}

– 예제는 UILabel 의 RedLabel 을 SwiftUI 에서 사용하는 경우입니다.


2. SwiftUI 에서 UIViewController 를 추가하기.

✔️1. 에서 UIViewRepresentable의 사용이 필수였다면,
UIViewController를 SwiftUI에서 사용하기 위해서는 UIViewControllerRepresentable가 필수입니다.

✔️UIViewControllerRepresentable를 구현하기 위해서는 반드시 두 가지 메소드를 구현해야 합니다.
func makeUIViewController(context: Context) -> some UIViewController
func updateUIViewController(_ uiViewController: some UIViewController, context: Context)

struct ImagePickerController: UIViewControllerRepresentable {
    func makeUIViewController(context: Context) -> UIImagePickerController {
        UIImagePickerController()
    }

    func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
}

struct ContentView: View {
    var body: some View {
        ImagePickerController()
    }
}

– SwiftUI 에 아직 같은 기능을 하는 View가 존재하지 않는 UIImagePickerController 입니다.

UIImagePickerController에서 선택한 UIImage를 Coordinator를 사용해서 SwiftUI에 표시해보죠.

struct ImagePickerController: UIViewControllerRepresentable {

    // 👀 SwiftUI 에서의 부모뷰의 @State property부터의 Binding
    @Binding var selectedImage: Image?
    @Binding var existSelectedImage: Bool

    func makeUIViewController(context: Context) -> UIImagePickerController {
        let imagePickerController = UIImagePickerController()
        imagePickerController.delegate = context.coordinator

        return imagePickerController
    }

    func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}

    // 💫 2. Cordinator 대입.
    func makeCoordinator() -> Coordinator {
        Coordinator(selectedImage: $selectedImage, existSelectedImage: $existSelectedImage)
    }

}

extension ImagePickerController {

    // 💫 1. Cordinator 만들기
    final class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
        // 👀 SwiftUI 에서의 부모뷰의 @State property부터의 Binding
        @Binding var selectedImage: Image?
        @Binding var existSelectedImage: Bool

        init(selectedImage: Binding<Image?>, existSelectedImage: Binding<Bool>) {
            _selectedImage = selectedImage
            _existSelectedImage = existSelectedImage
        }

        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            guard let selectedOriginalImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage else { return }

            selectedImage = Image(uiImage: selectedOriginalImage)
            existSelectedImage = true
        }
    }

}

struct ContentView: View {

    @State private var selectedImage: Image?
    @State private var existSelectedImage = false

    var body: some View {
        ZStack {
            VStack {
                selectedImage?
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width: 100.0, height: 100.0)
                Button(action: didTapSelectedImageButton) {
                    Text("Select Image")
                }
            }

            if selectedImage == nil {
                ImagePickerController(
                    selectedImage: $selectedImage,
                    existSelectedImage: $existSelectedImage
                )
            }
        }
    }

    private func didTapSelectedImageButton() {
        selectedImage = nil
        existSelectedImage = false
    }
}

init 에서 self.변수명 이 아니라 _변수명인지에 대해서 참고 👉 🔗
@State propertyBinding 으로 Coordinator에서 부모View에 update를 별도로 하지않아도 간단하게 구현되었어요 👏



그러면, 기존에 UIView 또는 UIViewController 에서 SwiftUI View를 사용할 때에는
UIViewRepresentable를 따르는 Struct를 따로 매번 만들어줘야할까요?

@Binding 할 property가 필요없는 경우에는 UIKit class 에서 extension 으로 구현이 가능해요.


final class Label: UILabel {}

// 💫 Extension 으로 정의
extension Label: UIViewRepresentable {
    func makeUIView(context: Context) -> UILabel {
        backgroundColor = .gray
        text = "This is UIKit UILabel"

        return self
    }

    func updateUIView(_ uiView: UILabel, context: Context) {}
}

struct ContentView: View {
    var body: some View {
        VStack {
            Text("This is Swift UI Text")
            Label()
        }
    }
}

– 이건 적절하게 사용하면 더 간단한 코드로 구현이 가능하지만, SwiftUI와 UIKit가 동시에 많이 사용되는 프로젝트의 경우에는 코드의 통일성을 생각하면서 구현해야할 듯 해요.



3. UIKit 에서 SwiftUI를 추가하기.

✔️UIHostingController 을 사용해야 합니다.
– 별도로 추가해야하는 method나 property는 없습니다.
UIHostingController(rootView: UIKit에서_표시할_SwiftUI) : SwiftUI View를 초기화해서 넣어주기만 하면 됩니당 가장 쉽쥬 😖


struct ContentView: View {
    var body: some View {
        Text("펭 - 하!")
            .fontWeight(.bold)
    }
}

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        // 💫
        let hostingController = UIHostingController(rootView: ContentView())
        hostingController.view.translatesAutoresizingMaskIntoConstraints = false
        hostingController.view.frame = view.bounds

        addChild(hostingController)
        view.addSubview(hostingController.view)
    }
}

UIHostingControllerrootView로 SwiftUI 를 넣어주기만 하면 끝 !
addChild, addSubView 는 지금까지의 UIKit에서 UIViewController를 추가시킬 때와 같답니다.



struct ContentView: View {
    var body: some View {
        Text("펭 - 하!")
            .fontWeight(.bold)
    }
}

class ViewController: UIViewController {

    @IBAction private func didTapPresentButton(_ sender: Any) {
        let hostingController = UIHostingController(rootView: ContentView())
        present(hostingController, animated: true)
    }

}

– SwiftUI 화면으로 present 시킬 때도 SwiftUI View를 UIHostingController로 감싸는 것 이외의 UIKit 코드는 지금까지와 동일하게 사용하시면 되어요.





SwiftUI 는 iOS 13부터 사용이 가능하기도 하고 기존 코드는 이미 UIKit 으로 구성되어있기 때문에
Objc에서 Swift로 코드를 이행할 때와 같이 여러가지 시행착오를 거쳐야 팁? 이 많이 생길 것 같아요.


오늘은 즐거운 토욜이네요 !
제가 얼마전에 새 집으로 이사를 와서 오늘 집들이를 한답니다 😉 TMI
ㅋㅋㅋㅋ

여러분 오늘도 즐거운 iOS 개발되세용




디지털 시대의 시선] 펭수, 탈 쓰고 살아가는 모든 어른이에게 - 모비 ...


“SwiftUI | UIKit 과 함께 사용하기. UIViewRepresentable, UIViewControllerRepresentable, UIHostingController”의 2개의 생각

  1. 저 혹시 preview 사용시 viewdidload 에 함수를 실행시키면 프리뷰에러가 나오는데 혹시 어떻게 해결할수있을까요?
    root로 함수를 보내면될까요?

    좋아요

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Google photo

Google의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중