본문 바로가기

iOS/SwiftUI

[SwiftUI] UIViewControllerRepresentable로 ImagePicker (카메라) 사용하기

지난 포스팅에서 소개했던 UIViewRepresentable에 이어, 이번에는 UIViewControllerRepresentable을 사용해보겠습니다.

 

 

UIViewControllerRepresentable은 UIViewRepresentable과 마찬가지로 UIViewControllerSwiftUI View로 통합시켜주기 위해 사용합니다. 이번에는 UIKit의 UIImagePickerController를 이용하여 카메라를 사용해보겠습니다.

 

앱에서 카메라를 사용하기 위해서는 먼저 사용자로부터 권한을 획득해야합니다.

 

카메라 권한 허용 여부를 묻는 팝업을 나타내려면, Info.plist에 카메라를 사용하는 이유를 작성해야 합니다. 

privacy - Camera Usage Decription 에 카메라 권한이 필요한 이유를 작성

이 때, 내용을 명확하게 작성하지 않으면 리젝 사유가 될 수 있으니 주의가 필요합니다. ("카메라 씀", "카메라 사용하겠습니다" 등등... 왜 사용하는지 또는 카메라를 통해 사용자가 앱에서 어떤 활동을 할 수 있는지 등에 대한 내용이 있어야 합니다. 다른 상용화된 앱을 참고해도 좋을 것 같습니다.) 

 

 

Info.plist에 필요한 내용을 작성하셨다면, 이제 사용자에게 카메라 권한을 요청해야 합니다.

//권한 여부 확인
let status = AVCaptureDevice.authorizationStatus(for: .video)

if status == .authorized {
    viewModel.showCameraView = true
} else if status == .notDetermined {
    //카메라 접근 권한 요청
    AVCaptureDevice.requestAccess(for: .video) { accessGranted in
        DispatchQueue.main.async {
            if accessGranted {
                viewModel.showCameraView = true
            } else {
                //카메라 접근 권한 없음 팝업
                viewModel.showNoCameraPermissionPopUp = true
            }
        }
    }
} else {
    //카메라 접근 권한 없음 팝업
    viewModel.showNoCameraPermissionPopUp = true
}

AVCaptureDevice의 authorizationStatus 메소드는 현재 카메라 권한 상태를 리턴합니다.

.authorized 는 권한이 허용되어있다는 뜻입니다. 이 때에는 카메라 뷰 (ImagePicker View)를 화면에 나타나게 해주면 됩니다.

.notDetermined 는 사용자가 아직 권한 허용 여부를 결정하지 않았다는 뜻입니다. 이 상태에서는 사용자에게 카메라 권한을 요청해야 합니다.

.denied 는 사용자가 카메라 사용을 거부했다는 뜻입니다. 이 경우에는 카메라 뷰를 보여줄 수 없기 때문에, 권한이 없음을 알려주는 팝업 화면을 보여주는 등의 절차를 구현하는 것이 좋습니다.

 

.notDetermined 상태에서, AVCaptureDevice의 requestAccess 메소드를 사용하면 팝업 형태로 사용자에게 권한을 요청합니다.

사용자에게 카메라 권한 허용 여부를 묻는 팝업

 

이제 UIViewControllerRepresentable을 사용할 때가 왔습니다.

UIViewControllerRepresentable 프로토콜을 채택하면 다음 두 가지 메소드를 필수적으로 구현해야합니다.

 

  • makeUIViewController
var sourceType: UIImagePickerController.SourceType = .camera

func makeUIViewController(context: Context) -> UIImagePickerController {
    let picker = UIImagePickerController()
    picker.sourceType = sourceType
//        picker.showsCameraControls = false
    picker.delegate = context.coordinator

    return picker
}

        → UIViewController를 생성하는 메소드입니다. 이 메소드가 리턴한 UIViewController가 SwiftUI View로 통합됩니다.

 

  • updateUIView
func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {
    //do nothing
}

        → State의 값이 변경되면 호출되는 메소드입니다. 

 

 

 

import SwiftUI

class ImagePickerCoordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
    
    @Binding var image: UIImage
    @Binding var isShown: Bool
    
    init(image: Binding<UIImage>, isShown: Binding<Bool>) {
        _image = image
        _isShown = isShown
    }
    
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        if let uiImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
            print(#fileID, #function, #line, "image selected")
            
            image = uiImage
            isShown = false
        }
    }
    
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        isShown = false
    }
}

struct ImagePicker: UIViewControllerRepresentable {
    typealias UIViewControllerType = UIImagePickerController
    typealias Coordinator = ImagePickerCoordinator
    
    @Binding var image: UIImage //카메라를 통해 얻은 사진을 ParentView로 넘겨주기 위한 Binding 변수
    @Binding var isShown: Bool //ImagePicker View의 화면 표시 여부
    
    var sourceType: UIImagePickerController.SourceType = .camera
    
    func makeUIViewController(context: Context) -> UIImagePickerController {
        let picker = UIImagePickerController()
        picker.sourceType = sourceType
//        picker.showsCameraControls = false
        picker.delegate = context.coordinator
        
        return picker
    }
    
    func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {
        //do nothing
    }
    
    func makeCoordinator() -> ImagePicker.Coordinator {
        return ImagePickerCoordinator(image: $image, isShown: $isShown)
    }
}

UIViewControllerRepresentable을 이용한 ImagePicker View의 코드입니다.

UIImagePickerController를 통해 얻은 사진 처리를 위해 UIImagePickercontrollerDelegate를 Coordinator 패턴으로 구현하였습니다. 

 

카메라를 통해 얻은 사진을 Binding 변수를 이용해서 부모 View로 넘겨줍니다.

저는 이 ImagePicker View를 SwiftUI의 NavigationLink를 이용하여 화면에 나타나게 구현하였고,

카메라 화면에서 "cancel" 버튼이 눌렸을 경우 ImagePicker View를 dismiss 하기 위해 isShown 이라는 Binding 변수를 사용하였습니다.