PhotoKit을 알아보자 - 2

6848 단어 swiftiOSiOS

(이 이미지는 왜 올라간 것일까.. 너 뭐야 ㅠ 나 안올렸다고)

이번에는 직접 Photo 프레임워크를 이용해서 유저의 사진 앨범에 접근을 해서 collectionView에 사진을 불러오거나 바로 카메라에 접근을 해서 이미지를 불러올 것이다.

콜렉션 뷰의 첫번째 아이템(카메라 이미지)을 선택하면 카메라로 넘어가고, 그 외 아이템들을 클릭하면 해당하는 이미지들을 가져올 수 있다.

중요! 앱이 유저의 카메라와, 앨범에 접근하려면 접근권한이 필요하다. info.plist로 이동한다.

다음과 같이 세 항목을 추가한다.

//MARK: - tempViewController
// 앨범에 접근해서 PHAsset 배열을 가져온 다음에 버튼을 눌러 뷰 전환을 할 때 collectioView에 넘겨줄 것이다.
import UIKit
import Photos
class tempVC: UIViewController {

	let btn: 생략
	var allPhotos: PHFetchResult<PHAsset>!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //...//
        
        fetchAsset()
    }
    
    private func fetchAsset() {
        let allPhotoOptions = PHFetchOptions()
        allPhotoOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
        allPhotos = PHAsset.fetchAssets(with: allPhotoOptions)
        
    }
    private func tapButton() {
        var navigationController: UINavigationController
        let targetVC = testCollectionViewer
        
        // 여기가 핵심. targetVC에 있는 변수인 fetchResult에 넘겨준다.
        targetVC.fetchResult = allPhotos
        
        navigationController = UINavigationController(rootViewController: targetVC)
        navigationController.modalPresentationStyle = .fullScreen
        
        present(navigationController, animated: true)
    }
    
}

다음으로는, PHFetchResult을 넘겨받아서 collectionView에 띄어주는 viewController를 만들 것이다. collectionView 생성 및 화면 배치는 생략할 것이다.

이전 챕터에서 언급 했듯이, fetchResult의 결과는 메타데이터다. 우리는 실제 이미지를 받아와야하기 때문에, 이것을 기반으로 image를 요청해야한다. 그러기 위해서 PHImageManager라는 것이 필요하다.
PHImageManger에 구현되어 있는 method requestImage를 통해 이미지를 받아올 수 있다. 코드로 확인해보자.

//MARK: - testCollectionViewController

class testCollectionViewController: UIViewController {
	// UIComponent
    
	var pictureCV = UICollectionView().then {//이하 생략}

	// Variable & properties

	var fetchResult: PHFetchResutl<PHAsset>!
    var photoImage: UIImage? // 여기에 카메라로 찍은 사진 저장


	fileprivate let imageManager = PHImageManager()
	fileprivate var thumbnailSize: CGSize!

	//MARK: - View Life Cycle
    
    override func viewDidLoad() {
        super.viewDidLoad()
        //...생략 //
    }
    
    override func viewWillAppear(_ animated: Bool) {
        let scale = UIScreen.main.scale
        let cellSize = pictureCV.size
        thumbnailSize = CGSize(width: cellSize.width * scale, 
        					   height: cellSize.height * scale)
                         
    }
}
extension testCollectionViewController: UICollectionViewDataSource {
	func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return fetchResult.count + 1
        // 카메라 이미지를 넣어야 하기 때문에, 
    }
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    guard let pictureCell =
                collectionView.dequeueReusableCell(withReuseIdentifier: "PictureCell",
                                                   for: indexPath) as? PictureCell else
        { return UICollectionViewCell() }
        if indexPath.item == 0 {
        	pictureCell.thumbnailImage = UIImage(systemName: "camera")
            return pictureCell
        } else {
        	let asset = fetchResult.object(at: (indexPath.item)-1 )
            
            pictureCell.representedAssetIdentifier = asset.localIdentifier
            imageManager.requestImage(for: asset, targetSize: thumbnailSize, contentMode: .aspectFill, options: nil, resultHandler: { image, _ in
                if pictureCell.representedAssetIdentifier == asset.localIdentifier {
                    pictureCell.thumbnailImage = image
                }
            })
            
            return pictureCell
        }
    }
}   

asset은 unique한 localIdentifier를 가진다고 했는데 각각의 cell에 이 localId를 넣어주는 게 중요하다. 왜냐하면 requestImage를 통해 가져오는 UIImage의 이름은 계속 바뀌기 때문이다. 그래서 데이터처리를 할 때 UIImage name으로 하는 것이 아니라 identifier로 처리를 해야만 한다.

cellClass는 다 적지는 않겠지만 이런 식으로 구성한다.

MARK: Cell Class

class PictureCell: UICollectionViewCell {
    
    var photoImageView = UIImageView().then{
        $0.backgroundColor = .black
    }
    var thumbnailImage: UIImage! {
        didSet {
            photoImageView.image = thumbnailImage
        }
    }
    var representedAssetIdentifier: String!

//... 생략//
}

마지막으로 카메라 이미지를 선택했을 때 카메라가 present로 올라와서 찍은 이미지를 변수에 저장하는 것이다.

MARK: -

class testCollectionViewCotroller: UIViewController {

///...//
	func didTapCamera() {
		let camera = UIImagePickerController()
        	camera.sourceType = .camera
        	camera.cameraDevice = .rear
        	camera.cameraCaptureMode = .photo
        	camera.delegate = self
        	present(camera, animated: true, completion: nil)
            }

}

extension testCollectionViewCotroller: UIImagePickerControllerDelegate & UINavigationControllerDelegate {
    
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        // 미디어 종류 확인
        let mediaType = info[UIImagePickerController.InfoKey.mediaType] as! NSString
        // 미디어가 사진이면
        if mediaType.isEqual(to: kUTTypeImage as NSString as String){
            // 사진을 가져옴
            let captureImage = info[UIImagePickerController.InfoKey.originalImage] as! UIImage
            
            // 사진을 포토 라이브러리에 저장. 안하려면 없애주면 된다.
            UIImageWriteToSavedPhotosAlbum(captureImage, self, nil, nil)
            photoImage = captureImage
            
        }
        // 현재의 뷰(이미지 피커) 제거
        self.dismiss(animated: true, completion: {
            self.didTapReset()
            let targetVC = tempCV()
            targetVC.photoImage = self.photoImage
            self.navigationController?.pushViewController(targetVC, animated: true)
            
        })
    }
    
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        self.dismiss(animated: true, completion: nil)
    }
    
}

이렇게 하면, 카메라에 접근할 수 있을 것이다. 다만 위의 영상에는 카메라를 누르지 않는데, 시뮬레이터이기 때문에 테스트를 해볼 수 없기 때문이다. 아이폰으로 테스트를 한다면 성공적으로 작동한다.

-끝-

좋은 웹페이지 즐겨찾기