SwiftUI

[SwiftUI] UIViewControllerRepresentable프로토콜로 갤러리에서 이미지 선택하기

하이D:) 2023. 6. 20. 14:04

UIKit의 view controller인 UIImagePickerController를 사용함으로써 SwiftUI에서 사진을 선택할 수 있는 기능을 구현할 수 있다

 

# 🥨 UIView와 UIViewController

- UIView : An object that manages the content for a rectangular area on the screen.

UiView는 공식문서에서 말하는 것처럼 화면의 사각형 영역에 대한 내용을 관리하는 객체이다. 우리가 사용하는 UILabel, UIButton, UITextField 등 UI컴포넌트 들의 부모클래스가 UIView인 것이다.

https://developer.apple.com/documentation/uikit/uiview

 

UIView | Apple Developer Documentation

An object that manages the content for a rectangular area on the screen.

developer.apple.com

 

- UIViewController : An object that manages a view hierarchy for your UIKit app.

UIViewController는 UIKit 앱의 뷰 계층 구조를 관리하는 객체이며, 사용자가 보고 있는 뷰에 대한 관리 기능을 제공한다. 실제로 XCode에서 UIKit를 기반으로 하는 앱을 하나 만들면 ViewController 이름을 가진 클래스가 기본적으로 하나 있고, UIViewController를 서브클래싱하고 있다. 

https://developer.apple.com/documentation/uikit/uiviewcontroller

 

UIViewController | Apple Developer Documentation

An object that manages a view hierarchy for your UIKit app.

developer.apple.com

 

 

# 🥨 UIViewRepresentable와 UIViewControllerRepresentable

즉, 우리는 SwiftUI에서 UIView를 쓰고 싶은 경우, UIViewController를 쓰고 싶은 경우 에 채택해야 하는 프로토콜이 다른데 각각 UIViewRepresentableUIViewControllerRepresentable이다. 이 글에서는 사진 선택 기능을 구현해주기 위해 UIViewControllerRepresentable를 사용해볼 것이고, UIViewRepresentable에 대해 궁금하다면 이전에 작성했던 아래 두 가지 글이 도움될 것이다.

 

SwiftUI에서 UIKit 사용하기 UIViewRepresentable (1) | TextField에서 clear button 사용하고 싶다면

SwiftUI에서 UIKit 사용하기 UIViewRepresentable(2) | @Binding 이란? Coordinator 란?

 

 

# 🥨 UIImagePickerController란?

UIImagePickerController는 사진 촬영, 동영상 녹화 및 사용자 미디어 라이브러리에서 항목 선택을 위한 시스템 인터페이스를 관리하는 뷰 컨트롤러이며 이 view controller를 사용해서 SwiftUI에서 사진 선택하는 기능을 구현할 것이다.

 

단, UIImagePickerController로는 여러개의 영상/사진을 컨트롤할 수 없고, 한번에 하나의 영상 혹은 사진을 선택할 수 있다. 여러개를 컨트롤할 수 있는 방법으로는 서드파티 API를 사용해야한다. 그렇기 때문에 이번에는 UIViewControllerRepresentable와 UIImagePickerController를 사용해서 fullScreen 혹은 sheet로 갤러리를 띄워서 내 디바이스에 있는 하나의 사진 혹은 영상을 선택하는 방법을 알아보도록하겠다.

 

 

# 🥨 UIViewControllerRepresentable 활용한 기능 구현

- SwiftUI에서 UIKit의 view controller를 사용해 주기 위해 UIViewControllerRepresentable를 채택한 구조체를 만들어준다.

- 필수적으로 정의해주어야하는 함수인 func makeUIViewController (context:) 와 func updateUIViewController (_:context:) 를 정의한다.

- 선택한 이미지의 정보를 바인딩하기 위해 Coordinator 클래스 선언. Coordinator는 UIImagePickerControllerDelegate의 역할을한다.

- SwiftUI뷰에서 선택한 이미지를 받기 위해 변수(selectedImage) 생성

- 이미지 선택에 따라  fullScreenCover 혹은 sheet 를 닫아주기 위해 관련 변수(showImagePicker) 생성 

 

struct ImagePickerPractice : UIViewControllerRepresentable {
    @Binding var image: UIImage?
    @Binding var showScreen: Bool
    
    
    func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePickerPractice>) -> UIImagePickerController {
        let viewContoller = UIImagePickerController()
        viewContoller.allowsEditing = false
        viewContoller.delegate = context.coordinator
        return viewContoller
    }
    
    func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePickerPractice>) {

    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
        
        var parent: ImagePickerPractice

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

        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {

            if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
                parent.image = image
                parent.showScreen = false
            }
        }
    }
    
}

 

- 실제로 뷰에 적용

struct UIViewControllerRepresentablePracticeView: View {
    @State private var showImagePicker : Bool = false
    @State private var selectedImage : UIImage?
    
    var body: some View {
        VStack{
            Button{
                self.showImagePicker = true
            } label : {
                Text("Open Image Picker")
                    .padding()
                    .background(.green)
                    .cornerRadius(10)
            }
            
            Button {
                print("selectedImage :", selectedImage)
            } label : {
                Text("Selected Image")
                    .padding()
                    .background(.blue)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            }
        }
//        .fullScreenCover(isPresented: $showImagePicker){
//            ImagePickerPractice(image: $selectedImage, showScreen: $showImagePicker)
//        }
        .sheet(isPresented: $showImagePicker){
            ImagePickerPractice(image: $selectedImage, showScreen: $showImagePicker)
        }
    }
}