본문 바로가기

UIKIT

PHPickerViewController, PHPickerConfiguration를 통해 이미지를 선택하는 방법

https://developer.apple.com/documentation/photokit/phpickerconfiguration

 

PHPickerConfiguration | Apple Developer Documentation

An object that contains information about how to configure a picker view controller.

developer.apple.com

https://zeddios.tistory.com/1052

 

iOS 14+ ) PHPicker

안녕하세요 :) Zedd입니다, WWDC를 보다가 정말정말..새로운것을 보게되어서 이렇게 공부하게 되었어요 :D 너무 재밌당 developer.apple.com/videos/play/wwdc2020/10652/ Meet the new Photos picker - WWDC 2020 - Videos - Appl

zeddios.tistory.com

 

https://developer.apple.com/documentation/photokit/phpickerviewcontroller

 

PHPickerViewController | Apple Developer Documentation

A view controller that provides the user interface for choosing assets from the photo library.

developer.apple.com

 

 

// MARK: - Actions
    /// 이미지 선택 버튼을 누르면 동작하는 함수
    @objc private func didTappedSelectedImages() {
        print("didTappedSelectedImages - called()")
        
        // PHPickerConfiguration 설정
        var configuration = PHPickerConfiguration()
        configuration.selectionLimit = 5   // 선택 가능한 이미지 또는 영상 개수
        configuration.filter = .any(of: [.images])   // 이미지 선택 가능
        
        let picker = PHPickerViewController(configuration: configuration)
        picker.delegate = self
        present(picker, animated: true)
    }

 

extension FeedViewController: PHPickerViewControllerDelegate {
    func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
        picker.dismiss(animated: true)
        
        let imageItems = results.prefix(5)
        
        for item in imageItems {
            item.itemProvider.loadObject(ofClass: UIImage.self) { (image, error) in
                if let image = image as? UIImage {
                    DispatchQueue.main.async {
                        print("Selected image: \(image)")
                    }
                }
            }
        }
    }
}

 

 

델리게이트 패턴을 적용

  • 역할 분리: FeedView는 버튼 클릭 이벤트를 발생시키는 역할만, 실제 처리는 FeedViewController가 담당.
  • 확장성: FeedView를 다른 화면에서도 재사용 가능.
  • 코드 구조 간결화: addTarget을 뷰 외부에서 중복적으로 호출할 필요 없음.
import UIKit

// 1. 델리게이트 패턴을 위한 프로토콜 설정
protocol FeedViewDelegate: AnyObject {
    func didTapSelectedImageButton()
}

class FeedView: UIView {
    
    // MARK: - Variables
    
    // 2. FeedViewDelegate를 채택한 delegate 변수 선언
    weak var delegate: FeedViewDelegate?
    
    // MARK: - UI Components
    let selectedButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("이미지 선택", for: .normal)
        button.backgroundColor = .systemBlue
        button.tintColor = .white
        button.layer.cornerRadius = 5
        button.addTarget(self, action: #selector(selectedButtonTapped), for: .touchUpInside)
        return button
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setupView() {
        addSubview(selectedButton)
        
        selectedButton.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            selectedButton.centerXAnchor.constraint(equalTo: centerXAnchor),
            selectedButton.centerYAnchor.constraint(equalTo: centerYAnchor),
            selectedButton.widthAnchor.constraint(equalToConstant: 120),
            selectedButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    @objc private func selectedButtonTapped() {
        delegate?.didTapSelectedImageButton()
    }
}

 

import UIKit
import PhotosUI

class FeedViewController: UIViewController {
    
    // MARK: - Variables
    var selectedImages: [UIImage?] = [UIImage(named: "new"), UIImage(named: "Stroll"), UIImage(named: "Tastes")]
    
    let feedView: FeedView = {
        let feedView = FeedView()
        return feedView
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemOrange
        configureConstraints()
        
        // 3. 대리자 선언 (대리자 = FeedViewController)
        feedView.delegate = self
        configureCollectionView()
    }
    
    private func configureConstraints() {
        view.addSubview(feedView)
        feedView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            feedView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 5),
            feedView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -5),
            feedView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 5),
            feedView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -5)
        ])
    }
    
    func configureCollectionView() {
        feedView.selectedImageCollectionView.delegate = self
        feedView.selectedImageCollectionView.dataSource = self
        feedView.selectedImageCollectionView.register(SelectedImageCollectionViewCell.self, forCellWithReuseIdentifier: SelectedImageCollectionViewCell.identifier)
    }
}

// 4. 델리게이틒 패턴에 따른, 함수를 구체적으로 생성 
extension FeedViewController: FeedViewDelegate {
    func didTapSelectedImageButton() {
        print("Delegate called: Button tapped.")
        presentImagePicker()
    }
}

// MARK: - PHPicker 관련 기능
extension FeedViewController: PHPickerViewControllerDelegate {

    private func presentImagePicker() {
        var configuration = PHPickerConfiguration()
        configuration.selectionLimit = 5
        configuration.filter = .images
        configuration.preferredAssetRepresentationMode = .automatic
        
        let picker = PHPickerViewController(configuration: configuration)
        picker.delegate = self
        present(picker, animated: true)
    }
    
    func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
        picker.dismiss(animated: true)
        let imageItems = results.prefix(5)
        
        for item in imageItems {
            item.itemProvider.loadObject(ofClass: UIImage.self) { (image, error) in
                if let image = image as? UIImage {
                    DispatchQueue.main.async {
                        print("Selected image: \(image)")
                    }
                }
            }
        }
    }
}

 

언제 addTarget을 쓰고, 언제 델리게이트를 써야 할까?

상황 적합한 선택
View 내부에서만 이벤트를 처리할 때 addTarget 사용
버튼 동작이 단순하고 Controller와 무관할 때 addTarget 사용
View와 Controller 간 역할을 명확히 분리할 때 델리게이트 사용
View를 여러 Controller에서 재사용할 때 델리게이트 사용
이벤트 처리 로직이 복잡하거나 확장이 필요할 때 델리게이트 사용

 

 

 

🔥 preferredAssetRepresentationMode

preferredAssetRepresentationMode는 **PHPickerConfiguration**에서 제공되는 속성으로, 사용자가 선택한 이미지 또는 동영상 파일을 어떤 형태로 앱에 전달할지 결정합니다. 이 속성을 사용하면 이미지나 동영상의 원본 데이터, 편집된 데이터, 또는 자동으로 적절한 데이터를 가져오는 방식을 지정할 수 있습니다.

 

설정값 (PHPickerConfiguration.AssetRepresentationMode)

  1. .automatic (기본값)
    • 시스템이 가장 적절한 형태로 데이터를 제공합니다.
    • 예: 사용자가 사진 앱에서 이미지를 편집한 경우, 편집된 이미지를 반환. 편집되지 않았다면 원본 이미지를 반환.
  2. .current
    • 사용자가 선택한 현재 상태의 데이터를 반환합니다.
    • 예: 편집된 이미지를 반환하며, 편집되지 않았다면 원본 이미지를 반환.
    • 주의: 데이터가 원본과 동일한 형식일 수도, 아닐 수도 있습니다.
  3. .compatible
    • 항상 가장 호환성이 높은 포맷으로 데이터를 반환합니다.
    • 예: 원본 이미지가 특정 형식(예: RAW)일 경우, JPEG와 같은 범용 포맷으로 변환하여 제공.

 

주요 사용 사례

  1. 편집 여부를 신경 쓰지 않을 때 (.automatic)
    • 기본값이며, 사용자가 사진 앱에서 이미지를 편집했는지 여부를 자동으로 처리.
    • 일반적인 앱에서 적합.
  2. 항상 편집된 결과를 필요로 할 때 (.current)
    • 사용자가 사진 앱에서 편집한 이미지를 그대로 사용하고 싶을 때.
  3. 원본 포맷의 호환성이 걱정될 때 (.compatible)
    • RAW 이미지 또는 비표준 포맷의 동영상이 포함될 수 있는 경우, JPEG나 MP4로 변환된 데이터를 사용.