ios/swift 이미지뷰에 앨범 이미지 넣기(이미지 뷰를 탭하여)


안녕하세요 kchoi입니다.

이번 포스트에서는 우리가 설정한 이미지뷰에 앨범에 있는 이미지를 넣어보도록 하겠습니다. 추가적으로 카메라 사진도 한번 넣어볼게요 ㅎㅎ

먼저 화면 구성은 다음과 같이 하였습니다.


스크린샷 2021-03-10 오후 4 22 49

ViewController 생성

먼저 우리 viewController부터 만들어볼까요?

저는 PostCommentViewController 라는 이름으로 만들었습니다.

viewController를 만드셨다면 이번 시간에 사용할

let imagePickerController = UIImagePickerController()

@IBOutlet weak var userCommentImageView: UIImageView!

만들어줍시다. 변수의 이름은 여러분이 원하는 대로 작성하시면 됩니다.


class PostCommentViewController: UIViewController {
    @IBOutlet weak var userCommentImageView: UIImageView!

    let imagePickerController = UIImagePickerController() 

    override func viewDidLoad() {
        super.viewDidLoad()
      ...
    }
}

alertAction 등록

우리는 이미지 뷰를 클릭하면 alertAction이 화면에 출력되고, 그 중 앨범에서 선택 이라는 항목을 클릭하면 앨범에 접근하여 이미지를 가져올 겁니다. 다른 항목 하나는 사진 촬영으로 둘 건데, 이것은 나중에 다루도록 하겠습니다.


먼저 viewController 내에 UIAlertController 하나를 만들어주도록 합시다. 저는 다음과 같이 만들었습니다.

let alertController = UIAlertController(title: "올릴 방식을 선택하세요", message: "사진 찍기 또는 앨범에서 선택", preferredStyle: .actionSheet)


그런 다음, viewDidLoad() 시점에서 해당 alertAction을 등록합니다.

함수 하나 안에 해당 로직을 넣고 호출을 할 건데, 이름은 enrollAlertEvent 정도로 하겠습니다.

override func viewDidLoad() {
  ...
  enrollAlertEvent()
}

func enrollAlertEvent() {
        let photoLibraryAlertAction = UIAlertAction(title: "사진 앨범", style: .default) {
            (action) in
            self.openAlbum() // 아래에서 설명 예정.
        }
        let cameraAlertAction = UIAlertAction(title: "카메라", style: .default) {(action) in
            self.openCamera() // 아래에서 설명 예정.
        }
        let cancelAlertAction = UIAlertAction(title: "취소", style: .cancel, handler: nil)
        self.alertController.addAction(photoLibraryAlertAction)
        self.alertController.addAction(cameraAlertAction)
        self.alertController.addAction(cancelAlertAction)
        guard let alertControllerPopoverPresentationController
                = alertController.popoverPresentationController
        else {return}
        prepareForPopoverPresentation(alertControllerPopoverPresentationController)
}

여기서 prepareForPopoverPresentation 부분은 익숙하지 않을 거라 예상하고 있습니다. 저 메소드에 대해 아직 설명하지 않았으니 당연히 현재 시점에선 오류가 뜰 겁니다.

먼저 prepareForPopoverPresentation 에 대해 설명하자면 alertAction 같이 popOver되는 메서드를 호출할 때, prepareForPopoverPresentation 함수를 구현해놓지 않으면 ipad 같은 기기에서 오류가 나기 때문에 애초에 빌드가 안 되게 막아놓았습니다.(xcode 12.4 version 기준.)

따라서 이것을 구현해주어야 빌드를 할 수 있는데, 저는 다음과 같이 구현을 해주었습니다.


아래의 .sourceView, .sourceRect는 팝오버되는 메시지의 위치를 조정합니다.

extension PostCommentViewController: UIPopoverPresentationControllerDelegate {
    func prepareForPopoverPresentation(_ popoverPresentationController: UIPopoverPresentationController) {
        if let popoverPresentationController =
      self.alertController.popoverPresentationController {
            popoverPresentationController.sourceView = self.view
            popoverPresentationController.sourceRect 
            = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
            popoverPresentationController.permittedArrowDirections = []
        }
    }
}

앨범에서 사진 고르기, 사진 찍기 메서드 구현하기

위에서 코드를 설명하지 않고 넘어간 부분이 있었죠?

바로 openAlbum()openCamera() 메서드였습니다.

openAlbum 함수는 앨범에서 사진을 선택할 수 있도록 해주는 메서드이고,

openCamera 함수는 사진을 찍어서 파일을 올릴 수 있게 만들어주는 메서드입니다.


imagePicker 선언, UIImagePickerControllerDelegate와 UINavigationControllerDelegate 선언.

그런데 이 메서드들을 구현하기 전에, 미리 해줘야 할 것이 있습니다.

바로 imagePicker를 선언하여 앨범에서 사진을 찍을 준비를 해줘야 한다는 건데요.

이를 위해 UIImagePickerControllerDelegate, UINavigationControllerDelegate 를 상속하도록 합시다.

...
let imagePickerController = UIImagePickerController() 

...

    override func viewDidLoad() {
    ...
    self.imagePickerController.delegate = self
    ...
  }
}

extension PostCommentViewController: UIImagePickerControllerDelegate,
UINavigationControllerDelegate {
    ...  
}

extension을 써서 코드가 복잡해보이지 않도록 관리해줍시다 !


여기서 잠깐! 왜 UIImagePickerControllerDelegate 만 상속받는 게 아니라 UINavigationControllerDelegate 까지 상속받는 걸까요?

여기에 대한 답은 zeddIOS 님께서 잘 정리를 해두었더라구요 ! 그래서 아래에 그분의 간략한 설명을 써두었습니다.

Q : UIImagePickerControllerDelegate를 선언 할 때 UINavigationControllerDelegate를 선언해야하는 이유를 분명히 알 수 있을까? 

A : 정확하게 번역을 못하겠어서, 여기저기서 찾은것을 종합했어요 ㅠㅠ

 UIImagePickerControllerDelegate의 delegate 속성은 UIImagePickerControllerDelegate와 UINavigationControllerDelegate 프로토콜을 모두 구현하는 객체로 정의되어있다. 

(위에서 해준 picker.delegate =  self) self를  picker.delegate에 할당하려면 self는 UINavigationControllerDelegate 타입이어야 한다. 

지금, picker의 델리게이트를 UINavigationControllerDelegate에 위임해준 것인데, 대리자는 사용자가 이미지나 동영상을 선택하거나 picker화면을 종료할 때, 알림을 받는다. 

info.plist에 row 추가.

앱에서 앨범에 접근해도 되는지, 카메라에 접근해도 되는지 물어봐야 해요.

그래서 다음 이미지의 빨간 색 네모 박스 속 두 줄을 추가해줍니다.


스크린샷 2021-03-11 오전 1 14 47

openAlbum() 함수와 openCamera() 함수 구현하기

먼저 openAlbum() 입니다.

func openAlbum() {
        self.imagePickerController.sourceType = .photoLibrary
        present(self.imagePickerController, animated: false, completion: nil)
}

openAlbum() 함수에서는 앨범의 sourceType을 지정하고, present 로 띄워주고 있어요.

이 때, 우리는 두 가지 처리를 더 해줘야 합니다.

하나는 우리가 선택한 이미지를 imageView에 설정하는 것.

다른 하나는 present한 view 를 dismiss 하는 것.

이것을 도와줄 친구가 func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) 입니다.

이 함수는 이미지를 선택하면 일어나는 로직을 담당하는 함수입니다.


구체적인 구현은 아래 코드를 확인해주세요.


func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        if let image = info[UIImagePickerController.InfoKey.originalImage]
            as? UIImage {
            userCommentImageView?.image = image
        }
        else {
            print("error detected in didFinishPickinMediaWithInfo method")
        }
        dismiss(animated: true, completion: nil) // 반드시 dismiss 하기.
    }

자, 그럼 이제 openAlbum()은 모두 구현했으니 openCamera() 함수를 구현해보도록 하겠습니다.


 func openCamera() {
        if (UIImagePickerController.isSourceTypeAvailable(.camera)) {
            self.imagePickerController.sourceType = .camera
            present(self.imagePickerController, animated: false, completion: nil)
        }
        else {
            print ("Camera's not available as for now.")
        } 
    }

카메라의 경우 시뮬레이터가 카메라를 쓸 수 없기 때문에 오류를 발생시킵니다.

이를 방지하기 위해 써줘야 하는 것이

if (UIImagePickerController.isSourceTypeAvailable(.camera)))

부분이 되겠네요.


이쯤되면 화면에 어서 출력을 해보고 싶죠?

이제 마지막 구현만이 남았습니다.


imageView를 탭 했을 때 alertController가 동작하도록 설정하기

이제, 이미지 뷰를 탭 했을 때 alert 메서드가 동작해도록 만들어줍시다.


원리는 이렇습니다. tap Recognizer가 imageView를 탭했는지 확인하고,

tapRecognizer가 탭을 감지했을 때 alertController를 호출하면 끝!

그런데 저희는 storyBoard에서 굳이 tapRecognizer를 imageView에 연결하지 않고,

코드로(programatically) 써서 우리가 원하는 것을 동작시킬 거예요.

한번 코드로 확인해보실게요~


viewDidLoad()에서 addGestureRecognizer() 함수를 호출한다고 가정하겠습니다. 이 경우 코드는

func addGestureRecognizer() {
        let tapGestureRecognizer 
  = UITapGestureRecognizer(target: self, 
                           action: #selector(self.tappedUIImageView(_:)))
        self.userCommentImageView.addGestureRecognizer(tapGestureRecognizer)
        self.userCommentImageView.isUserInteractionEnabled = true
}

위의 코드가 잘 이해 가시나요? userCommentImageView는 누르면 alertController가 띄워질 이미지 뷰에요.

그리고 tappedUIImageView(_:) 는 아직 작성하지 않았습니다. (아마 지금은 오류가 떠 있을 거예요.)

이것도 한번 작성해줍시다 !


@objc func tappedUIImageView(_ gesture: UITapGestureRecognizer) {
        self.present(alertController, animated: true, completion: nil)
}

너무 간단한가요?

사실 alertAction을 띄우는 것 외에는 따로 처리할 부분이 없어서 한 줄로 끝이 났습니다. 여기까지 잘 따라하셨다면 어떻게 화면에 출력이 되는지 확인해야겠죠? 아래 이미지를 확인해주세요 !


Mar-11-2021 01-33-04

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기
// custom