SwiftUI에서 UIImagePickerController 사용
카메라로 찍거나, 앨범에서 고른 사진을 변수에 저장하는 기능을 구현하려고 한다.
UIKit을 같이 사용해서 구현했다.
import SwiftUI
import UIKit
struct ContentView: View {
/// 이미지 피커를 보여줄지 여부를 결정하는 State
@State private var showingImagePicker = false
/// 선택한 이미지를 저장하는 State
@State private var inputImage: UIImage?
var body: some View {
VStack {
if let inputImage = inputImage {
// 선택한 이미지가 있으면 화면에 표시
Image(uiImage: inputImage)
.resizable()
.scaledToFit()
}
// 이미지 피커를 표시하는 버튼
Button("사진 선택") {
showingImagePicker = true
}
}
// .sheet를 .fullScreenCover로 변경
// present 여부를 $showingImagePicker로 결정함
// .sheet나 .fullScreenCover를 사용하면, 해당 뷰를 닫을 때 자동으로 isPresented와 연결된 변수 false로 설정
.fullScreenCover(isPresented: $showingImagePicker, onDismiss: loadImage) {
// 이미지 피커를 표시
ImagePicker(image: $inputImage)
}
}
/// 이미지가 선택되고, ImagePicker가 dismiss되면 실행되는 함수
func loadImage() {
// 이미지를 저장하거나 처리하려면 여기에서 수행
}
}
//
struct ImagePicker: UIViewControllerRepresentable {
/// 뷰의 presentation 상태에 접근하는 데 사용된다. 뷰를 닫는 동작을 처리하기 위해 사용
@Environment(\.presentationMode) var presentationMode
/// 선택한 이미지를 저장하는 변수. 다른 뷰와 값이 동기화되어야 하므로 @Binding이 사용됨
@Binding var image: UIImage?
// UIViewControllerRepresentable에 정의되어 있음
// UIViewController 객체가 생성됨과 동시에 호출, Coordinator 객체를 생성
func makeCoordinator() -> Coordinator {
// init 메서드에 자신(ImagePicker)넣음
Coordinator(self)
}
// UIViewControllerRepresentable을 채택한 뷰가 생성될 때 호출
// UIImagePickerController를 생성하고 반환
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let picker = UIImagePickerController()
// delegate를
picker.delegate = context.coordinator
// picker.sourceType = .camera
picker.sourceType = .photoLibrary // 앨범에서 이미지를 선택하도록 설정
return picker
}
// 이미지 피커를 업데이트하는 함수 (본 예제에서는 필요하지 않음)
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {}
// UIImagePickerControllerDelegate와 UINavigationControllerDelegate를 구현하는 Coordinator 클래스
class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
let parent: ImagePicker
init(_ parent: ImagePicker) {
self.parent = parent
}
// 이미지가 선택되면 호출되는 함수
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let uiImage = info[.originalImage] as? UIImage {
parent.image = uiImage
}
parent.presentationMode.wrappedValue.dismiss()
}
}
}
ImagePicker 구조체는 swiftUI에서 UIViewController를 사용하기 위해 UIViewControllerRepresentable 프로토콜을 채택했다.
이로써, SwiftUI 뷰 내에서 UIKit 기반 뷰 컨트롤러를 사용할 수 있으며, SwiftUI와 UIKit 간의 상호 운용성을 확보할 수 있다.
해당 프로토콜을 구현하기 위해서는 아래의 메서드를 구현해야한다.
makeUIViewController(context: Context) -> UIViewControllerType:
SwiftUI에서 사용할 UIViewController 인스턴스를 생성하고 초기 설정을 수행
updateUIViewController(_ uiViewController: UIViewControllerType, context: Context):
SwiftUI 뷰가 업데이트되면 호출되어, UIViewController 인스턴스를 업데이트하는 데 사용
대략적인 플로우를 설명하자면 이렇다.
버튼을 누르면 showingImagePicker값이 바뀌어 ImagePicker가 생성된다.
UIViewControllerRepresentable을 채택한 뷰인 ImagePicker가 생성될 때 makeUIViewController(context:)함수가 호출된다.
이 함수는 뷰의 응답 대상인 UIViewController 객체를 생성하고 반환한다.
UIViewController 객체가 생성됨과 동시에, makeCoordinator()함수가 호출되는데, 이 함수는 해당 뷰와 함께 동작하는 Coordinator 객체를 생성한다. 이때 파라미터로 self(ImagePicker)를 넣어서 parent에 저장한다.
이미지가 선택되면 imagePickerController(_: didFinishPickingMediaWithInfo:)가 호출되고 parent.image에 선택된 이미지를 저장한 후 parent를 dismiss한다.
dismiss하게되면 fullScreenCover의 isPresented와 연결된 변수는 자동으로 false로 설정된다.
SwiftUI만 써서 호다닥 카메라앱 만들기 feat.MVVM [Intro]
이 시리즈에서 할 일은~ 최대한 SwiftUI만 사용하여 기성품과 다름 없는 카메라 앱을 만드는 것이다.
enebin.medium.com
나중에 볼 링크