포스트

MapKit (5)

Profile 설정 화면 보강하기

첫번째 글에서 만들었던 프로필 설정 화면을 더 보완해보도록 한다.

AvatarView 수정

1
2
3
4
5
6
7
8
9
10
11
12
13
struct AvatarView: View {
    
    var image: UIImage // new
    var size: CGFloat
    
    var body: some View {
        Image(uiImage: image) // modified
            .resizable()
            .scaledToFit()
            .frame(width: size, height: size)
            .clipShape(Circle())
    }
}

image라는 변수를 만들어서 그부분을 이미지로 대체하도록 한다.

크게 언급할건 없어보인다.

이제는 Image가 image라는 변수를 받기에, 관련된 부분을 모두 수정해준다.

UIKit과 병행해서 사용하기

Picker를 사용할건데, 이때 사용하는 방식이 독특해서 시작부분을 적어본다.

UIViewControllerRepresentable Docs 참고.

UIkit을 사용할거라 UIViewControllerRepresentable를 사용한다.

Docs에도 있지만 간단하게 정리하면 VC object를 SwiftUI에 사용할때 쓴다.

1
2
3
4
struct PhotoPicker: UIViewControllerRepresentable {
    typealias UIViewControllerType = UIImagePickerController
       
}

이렇게 widget할떄처럼 typealias가 필요하며 이때 우리는 UIImagePickerController를 사용할 것이기에 이렇게 적어준다. UIImagePickerController Docs

그리고 fix를 하면

1
2
3
4
5
6
7
8
9
10
struct PhotoPicker: UIViewControllerRepresentable {
    func makeUIViewController(context: Context) -> UIImagePickerController {
        
    }
    
    func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {
        
    }
    
}

이렇게 함수가 추가된다 (이때 typealias는 지우도록 하자)

picker 만들기

1
2
3
4
5
func makeUIViewController(context: Context) -> UIImagePickerController {
    let picker = UIImagePickerController()
    picker.allowsEditing = true
    return picker
}

이렇게 picker에 대한 설정을 해준다.

여러 설정을 할 수 있지만 여기선 수정 허용만 해주었다.

예전에는 UIHostingController 사용해서 UIkit의 UIView를 사용했는데 그거와는 조금 다르다.

Coordinator 만들기

UIkit과 SwiftUI 사이에 일종의 Pipe라고 생각을 하면 될것 같다.

Image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct PhotoPicker: UIViewControllerRepresentable {
    
    func makeUIViewController(context: Context) -> UIImagePickerController {
    // 생략
    }
    
    func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
    
    // new

    func makeCoordinator() -> Coordinator {
        
    }

    final class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
        
        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            
        }
    }
     
}

위와같이 먼저 final class로 Coordinator라는 클래스를 만들게 되면 또 fix하라는 에러가 뜨고 fix를 해주면 makeCoordinator라는 함수가 자동으로 생성이 된다.

이때 Coordinator에는 우리가 사용할때 필요한 Delegate Protocol이 필요하다.

보통 UIkit을 사용했을땐, VC에 별도의 Extension을 사용하여 Delegate Protocol를 채택하여 사용 했는데, Coordinator 가 그역할을 대신 해준다고 생각하면 좋을듯하다.

그리고 그 안에서 우리는 didFinishPickingMediaWithInfo라는 함수를 사용한다.

해당함수는 UIImagePickerControllerDelegate에서 가져온다.

그리고 Coordinator에 picker를 만들고 initializing을 해준다.

mmakeCoordinator 함수는 말그대로 Coordinator를 만들어주는 함수인데, Coordinator 인스턴스를 생성하면서 PhotoPicker(struct)를 초기화 값으로 전달한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
final class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
    
    let photoPicker: PhotoPicker
    
    init(photoPicker: PhotoPicker) {
        self.photoPicker = photoPicker
    }
    // 함수 생략
}

func makeCoordinator() -> Coordinator {
    Coordinator(photoPicker: self)
}

이후에 picker를 설정했던곳에 UIkit 사용시 반드시 필요한 delegate 설정을 해준다.

1
2
3
4
5
6
func makeUIViewController(context: Context) -> UIImagePickerController {
    let picker = UIImagePickerController()
    picker.delegate = context.coordinator // new
    picker.allowsEditing = true
    return picker
}

그리고 다시 ProfileView로 와서

1
2
3
4
5
6
7
8
9
@State private var isShowingPhotoPicker = false

VStack {
    // 생략
        }
        .navigationTitle("Profile")
        .sheet(isPresented: $isShowingPhotoPicker) {
            PhotoPicker(image: $avatar)
        }

sheet Modifier를 추가해준다.

picker를 띄우기 위한 모디파이어라고 생각하면 된다.

이후 다시 PhotoPicker로 돌아와서 didFinishPickingMediaWithInfo 함수에 내용을 추가.

여기선 이미지를 유저가 선택했을때의 액션인데, 프로필 사진에 굳이 선택한이미지가 온전하게 들어갈 필요가 없으므로 (프로필 이미지 박스 자체가 사이즈가 작기에), 이미지 압축을 한다.

우리는 10%만 남긴다.

1
2
3
4
5
6
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
    if let image = info[.editedImage] as? UIImage {
        let compressedImageData = image.jpegData(compressionQuality: 0.1)!
        photoPicker.image = UIImage(data: compressedImageData)!
    }
}

떡볶이 프로젝트를 할때도 유사한 내용이 있다.

나중에 다시 기억을 살릴겸 읽어보면 좋을듯.

ProfileView TapGesture 추가를 해주자.

간단하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
var body: some View {
    VStack {
        ZStack {
            NameBackgroundView()
            
            HStack(spacing: 16) {
                ZStack {
                   // 생략
                }
                .padding(.leading, 12)
                .onTapGesture { // new
                    isShowingPhotoPicker = true
                }

true로만 바꿔주면 된다.

Image

하지만 현재는 이미지를 선택하고 choose를 해도 되지 않는다.

선택후에 picker를 dismiss하는 부분이 빠졌기 때문.

여기선 dismiss를

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Environment(\.presentationMode) var presentationMode

final class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
    
    // 생략
    
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        if let image = info[.editedImage] as? UIImage {
            //생략
        }
        
        photoPicker.presentationMode.wrappedValue.dismiss()
    }
}

이렇게 Environment를 사용했는데,

1
2
3
4
5
6
7
8
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
    if let image = info[.editedImage] as? UIImage {
        let compressedImageData = image.jpegData(compressionQuality: 0.1)!
        photoPicker.image = UIImage(data: compressedImageData)!
    }
    
    picker.dismiss(animated: true)
}

이렇게 picker의 dismiss를 사용해도 된다.

dismiss처리가 끝나고 실행해보면

Image

이렇게 이미지가 적용이 되는걸 알 수 있다.

ImagePicker를 사용할땐 반드시 dismiss처리를 하도록 하자.

이전에도 파이널때 사용을 했었으니 뭐…

이전글


Github: Dub-Dub-Grub Repository

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.