๐Ÿ’ก work work work/swift

swift ์‹ค์Šต - 5 : Photo Gallery App

hanwitjus 2022. 2. 22. 14:40

์˜ค๋Š˜ ํ•œ๊ฑด ๊ฐค๋Ÿฌ๋ฆฌ์—์„œ ์‚ฌ์ง„์„ ๊ฐ€์ ธ์™€์„œ ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ์— ๋Œ€ํ•ด์„œ ๋ฐฐ์› ๋‹ค.

 

 

 

 

์™„์„ฑ๋œ ์•ฑ์˜ ์‹คํ–‰ํ™”๋ฉด์ด๋‹ค.

 

1. UICollectionView, UICollectionViewCell๋กœ ๋ ˆ์ด์•„์›ƒ ๋งŒ๋“ค๊ธฐ

2. ๋„ค๋น„๊ฒŒ์ด์…˜๋ฐ”์— ๋ฒ„ํŠผ ์ถ”๊ฐ€

3. ๊ฐค๋Ÿฌ๋ฆฌ ์ ‘๊ทผ ๊ถŒํ•œ ์ฃผ๊ธฐ

4. ๊ฐ€์ ธ์˜จ ์‚ฌ์ง„ ํ‘œ์‹œ ๋ฐ ๋กœ๋”ฉํ•˜๊ธฐ

 

์ด ์ˆœ์„œ๋Œ€๋กœ ๋ฐฐ์šด ๋‚ด์šฉ์„ ์ •๋ฆฌํ•ด๋ณด๋ ค๊ณ  ํ•œ๋‹ค.

 

 

 

 

 

 

 

1. ๋ ˆ์ด์•„์›ƒ ๋งŒ๋“ค๊ธฐ

์Šคํ† ๋ฆฌ๋ณด๋“œ์—์„œ UICollectionView๋ฅผ ์ถ”๊ฐ€ํ•ด์„œ ํ™”๋ฉด์— ๊ฝ‰ ์ฐจ๋„๋ก ์„ค์ •ํ•ด๋‘๊ณ 

UICollectionViewCell์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

 

๊ทธ๋Ÿผ ์ด๋Ÿฐ ๋ชจ์–‘์ด ๋œ๋‹ค.

 

๋ทฐ ์…€์€ ๋ทฐ๋งŒ ์„ค์ •ํ•ด๋„ ํ•˜๋‚˜๊ฐ€ ์ƒˆ๋กœ ์ƒ๊ธฐ๊ณ , ๊ทธ ์•ˆ์— ์ด๋ฏธ์ง€๋ทฐ๋ฅผ ๋„ฃ์–ด์„œ ๊ทธ ์…€ ์•ˆ์— ๊ฝ‰ ์ฐจ๋„๋ก ์„ค์ •์„ ํ•˜๋ฉด ๋œ๋‹ค.

์…€์— ๋Œ€ํ•ด์„œ๋Š” PhotoCell.swift ํŒŒ์ผ์„ ์ƒˆ๋กœ ์ƒ์„ฑํ•˜๊ณ , identifier์™€ class๋ชจ๋‘ PhotoCell๋กœ ์„ค์ •ํ•ด๋‘”๋‹ค.

 

 

2. ๋„ค๋น„๊ฒŒ์ด์…˜๋ฐ”์— ๋ฒ„ํŠผ ์ถ”๊ฐ€

Editor > Embed In > Navigation Controller 

๋ฅผ ๋ˆŒ๋Ÿฌ์„œ ๋„ค๋น„๊ฒŒ์ด์…˜ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

 

๊ทธ ์•ˆ์— ํƒ€์ดํ‹€๊ณผ ๋ฒ„ํŠผ์— ๋Œ€ํ•œ ์„ค์ •์€ 

๋ฉ”์ธ ViewController์˜ viewDidLoad์—์„œ

 override func viewDidLoad() {
        super.viewDidLoad()
        
        self.title = "Photo Gallery App"
        makeNavigationItem()
    }

์ด๋ ‡๊ฒŒ ์„ค์ •ํ•ด๋‘”๋‹ค.

 

๋ฒ„ํŠผ์„ ์ƒ์„ฑํ•˜๋Š” ์ฝ”๋“œ๋Š” ๊ธธ์–ด์ ธ์„œ

makeNavigationItem() ํ•จ์ˆ˜๋กœ ๋นผ๋†จ๋‹ค.

 

func makeNavigationItem(){
        let photoItem = UIBarButtonItem(image: UIImage(systemName: "photo"), style: .done, target: self, action: #selector(checkPermission))
        
        let refreshItem = UIBarButtonItem(image: UIImage(systemName: "arrow.clockwise"), style: .done, target: self, action: #selector(refresh))
        
        photoItem.tintColor = .black.withAlphaComponent(0.7)
        
        self.navigationItem.rightBarButtonItem = photoItem
        
        self.navigationItem.leftBarButtonItem = refreshItem
    }

photoItem์ด ์‚ฌ์ง„ ๋ชจ์–‘ ์•„์ด์ฝ˜ ๋ฒ„ํŠผ์ด๊ณ  refreshItem์ด ์ƒˆ๋กœ๊ณ ์นจ์„ ํ•˜๋Š” ๋ฒ„ํŠผ์ด๋‹ค.

 

๋‘˜๋‹ค ์‚ฌ์ง„ ์•„์ด์ฝ˜์ธ๋ฐ, SF Symbols๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‹œ์Šคํ…œ ์ด๋ฏธ์ง€๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.

photoItem์€ withAlphaComponent๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํšŒ์ƒ‰์œผ๋กœ ์„ค์ •ํ•ด๋’€๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  ๊ฐ๊ฐ์˜ ๋ฒ„ํŠผ์ด ํ•˜๋Š” ์ผ์€ UIBarButtonItem์„ ์‚ฌ์šฉํ• ๋•Œ #selector๋กœ objc ๋Ÿฐํƒ€์ž„ ํ•จ์ˆ˜๋ฅผ ๋„ฃ์–ด์คฌ๋‹ค.

 

refresh ๋ฒ„ํŠผ์— ๋Œ€ํ•ด์„œ๋Š” ๊ทธ๋ƒฅ ๋ฐ์ดํ„ฐ๋ฅผ reload ํ•ด์˜ค๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์—

@objc func refresh(){
        self.photoCollectionView.reloadData()
    }

์ด๋ ‡๊ฒŒ ์„ค์ •์„ ํ•ด์คฌ๊ณ ,

์‚ฌ์ง„ ์ด๋ฏธ์ง€ ๋ฒ„ํŠผ์— ๋Œ€ํ•ด์„œ๋Š” ๋จผ์ € ๊ฐค๋Ÿฌ๋ฆฌ์˜ ์ ‘๊ทผ๊ถŒํ•œ์ด ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์—

์ด๋ฅผ ์ฒดํฌํ•˜๊ณ  ๊ฐ๊ฐ ๊ถŒํ•œ ํ—ˆ์šฉ ์„ค์ • ์ƒํƒœ์— ๋”ฐ๋ผ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•  ์ผ์„ ์ฝ”๋”ฉํ•ด์ค€๋‹ค.

 

 

3. ๊ฐค๋Ÿฌ๋ฆฌ ์ ‘๊ทผ ๊ถŒํ•œ ์ฃผ๊ธฐ

๊ฐค๋Ÿฌ๋ฆฌ ์ ‘๊ทผ๊ถŒํ•œ์— ๋Œ€ํ•œ ์ƒํƒœ๋Š” ํฌ๊ฒŒ 5๊ฐ€์ง€๊ฐ€ ์žˆ๋‹ค.

 

  • .authorized : ํ—ˆ์šฉ๋œ ์ƒํƒœ
  • .limited : ๋ถ€๋ถ„ ํ—ˆ์šฉ๋œ ์ƒํƒœ
  • .denied : ๊ฑฐ๋ถ€๋œ ์ƒํƒœ
  • .notDetermined : ๊ถŒํ•œ ์„ค์ •์„ ์š”์ฒญ๋ฐ›์ง€ ์•Š์€ ์ƒํƒœ
  • .restricted : ์–ด๋–ค ์š”์ธ์—์„œ๋“ ์ง€ ๊ถŒํ•œ ์„ค์ •์„ ํ•  ์ˆ˜ ์—†๋Š” ์ƒํƒœ

 

์—ฌ๊ธฐ์„œ ๋งˆ์ง€๋ง‰ restricted๋Š” ๊ฐœ๋ฐœ ๋‹จ๊ณ„์—์„œ ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆ˜ ์—†๋Š” ๋ฌธ์ œ์ด๊ธฐ ๋•Œ๋ฌธ์— ์ œ์™ธํ•˜๊ณ ,

๋‚˜๋จธ์ง€ 4๊ฐ€์ง€์— ๋Œ€ํ•ด์„œ ์„ค์ •ํ•œ๋‹ค.

 

@objc func checkPermission() {
        
        if PHPhotoLibrary.authorizationStatus() == .authorized || PHPhotoLibrary.authorizationStatus() == .limited {
            DispatchQueue.main.async {
                self.showGallery()
            }
            
        }else if PHPhotoLibrary.authorizationStatus() == .denied{
            DispatchQueue.main.async {
                self.showAuthorizationAlert()
            }
            
        }else if PHPhotoLibrary.authorizationStatus() == .notDetermined{
            PHPhotoLibrary.requestAuthorization { status in
                print(status)
                self.checkPermission()
            }
        }
    }

๋„ค๋น„๊ฒŒ์ด์…˜๋ฐ”์— ์‚ฌ์ง„ ์•„์ด์ฝ˜ ๋ฒ„ํŠผ์—์„œ ์‚ฌ์šฉํ•  ํ•จ์ˆ˜์ด๊ธฐ ๋•Œ๋ฌธ์— object-c runtime์œผ๋กœ ์„ ์–ธํ•ด์ค€๋‹ค.

 

์ฒซ๋ฒˆ์งธ๋กœ authorized๋ž‘ limited์˜ ๊ฒฝ์šฐ์—๋Š” ๊ถŒํ•œ์ด ํ—ˆ์šฉ๋œ ๊ฒƒ์ด๋ฏ€๋กœ showGallery() ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ด์„œ ๊ฐค๋Ÿฌ๋ฆฌ๋ฅผ ๋ณด์—ฌ์ฃผ๋ฉด ๋œ๋‹ค.

๋‘๋ฒˆ์งธ denied์—์„œ๋Š” ๊ถŒํ•œ์ด ๊ฑฐ์ ˆ๋œ ๊ฒƒ์ด๋ฏ€๋กœ, ๊ถŒํ•œ์„ ํ™œ์„ฑํ™” ํ•  ๊ฒƒ์„ ์š”๊ตฌํ•˜๋Š” showAuthorizationAlert ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.

๋งˆ์ง€๋ง‰ notDetermined์—์„œ๋Š” ๊ถŒํ•œ ์„ค์ •์„ ๋ฌป์ง€๋„ ์•Š์€ ์ƒํƒœ์ด๊ธฐ ๋•Œ๋ฌธ์— ๊ถŒํ•œ ์„ค์ •์„ ํ•˜๋Š” ํ•จ์ˆ˜ checkPermission์„ ๋‹ค์‹œ ํ˜ธ์ถœํ•œ๋‹ค.

 

์—ฌ๊ธฐ์„œ ์‚ฌ์šฉํ•˜๋Š” ํ•จ์ˆ˜ showGallery์™€ showAuthorizationAlert์„ ์‚ดํŽด๋ณด๋ฉด

 

func showAuthorizationAlert(){
        let alert = UIAlertController(title: "ํฌํ†  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ ‘๊ทผ ๊ถŒํ•œ์„ ํ™œ์„ฑํ™” ํ•ด์ฃผ์„ธ์š”.", message: nil, preferredStyle: .alert)
        
        alert.addAction(UIAlertAction(title: "๋‹ซ๊ธฐ", style: .cancel, handler: nil))
        
        alert.addAction(UIAlertAction(title: "์„ค์ •์œผ๋กœ ๊ฐ€๊ธฐ", style: .default, handler: {
            action in
            guard let url = URL(string: UIApplication.openSettingsURLString) else{
                return
            }
            if UIApplication.shared.canOpenURL(url){
                UIApplication.shared.open(url, options: [:], completionHandler: nil)
            }
        }))
        
        self.present(alert, animated: true, completion: nil)
    }

 

๊ถŒํ•œ ์„ค์ •์„ ์œ„ํ•ด์„œ alert์ฐฝ์„ ๋„์šฐ๊ธฐ ์œ„ํ•ด UIAlertController๋กœ ์ƒ์„ฑํ•œ๋‹ค.

์„ค์ •์œผ๋กœ ๊ฐ€๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด์„œ URL๋กœ ์„ค์ •ํ•ด์ค€๋‹ค.

 

์—ฌ๊ธฐ์„œ ๊ฐค๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ์„ ์œ„ํ•ด ๋œจ๋Š” ๊ฒฝ๊ณ ์ฐฝ์—

์‚ฌ์ง„๋“ค์ด ์™œ ํ•„์š”ํ•œ์ง€ ์ ์–ด๋†“์•„์•ผ ๋‚˜์ค‘์— ์•ฑ์„ ์ถœ์‹œํ•  ๋•Œ ์‹ฌ์‚ฌ์— ํ†ต๊ณผํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ํ•œ๋‹ค.

 

๊ทธ๊ฑธ ์„ค์ •ํ•˜๋ ค๋ฉด Info.plist ํŒŒ์ผ์— ๋“ค์–ด๊ฐ€์„œ

์ด๋ ‡๊ฒŒ ์„ค์ •ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

 

๊ทธ๋Ÿผ Alert์— ์ด๋ ‡๊ฒŒ ๋œฌ๋‹ค.

 

 

4. ๊ฐ€์ ธ์˜จ ์‚ฌ์ง„ ํ‘œ์‹œ ๋ฐ ๋กœ๋”ฉํ•˜๊ธฐ

(์—ฌ๊ธฐ๊ฐ€ ์กฐ๊ธˆ ์–ด๋ ต๋‹ค,,, ์ œ๋Œ€๋กœ ์ดํ•ดํ–ˆ๋Š”์ง€๋„ ๋ชจ๋ฅด๊ฒ ๋‹ค. ๊ทธ๋ƒฅ ์ดํ•ดํ•œ ๋งŒํผ ์ •๋ฆฌํ•ด๋†“๋Š” ๊ฑธ๋กœ^^)

 

๋จผ์ € PhotosUI๋ฅผ import ํ•ด์•ผํ•œ๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  ๊ฐค๋Ÿฌ๋ฆฌ์—์„œ ์‚ฌ์ง„์„ ์„ ํƒํ•˜๊ณ  ๋‚œ ๋’ค ์ผ์–ด๋‚˜์•ผ ํ•  ์ผ๋“ค์— ๋Œ€ํ•ด์„œ๋Š” PHPickerViewControllerDelegate๋ฅผ ๊ตฌํ˜„ํ•ด์•ผํ•œ๋‹ค.

 

extension ViewController: PHPickerViewControllerDelegate {
    func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
        
        let identifiers = results.map{
            $0.assetIdentifier ?? ""
            
        }
        
        self.fetchResults = PHAsset.fetchAssets(withLocalIdentifiers: identifiers, options: nil)
        
        self.photoCollectionView.reloadData()
        
        self.dismiss(animated: true, completion: nil)
    }

๊ตฌํ˜„ํ•ด์•ผํ•  ํ•จ์ˆ˜๋Š” ์ด๋ ‡๊ฒŒ ์ƒ๊ฒผ๋Š”๋ฐ, ์ค‘์š”ํ•œ๊ฑด photo data๋“ค์ด ์‚ฌ์ง„ ํŒŒ์ผ ๊ทธ ์ž์ฒด๋กœ ์ „๋‹ฌ๋˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ 

๋””๋ฒ„๊น… ๋ถ€๋ถ„์„ ๋ณด๋ฉด

 

assetIdentifier๋ผ๊ณ  ์ € ์ฝ”๋“œ๋กœ ์ „๋‹ฌ์ด ๋œ๋‹ค. ๊ทธ๋ž˜์„œ ์ € ์ฝ”๋“œ๋ฅผ ๊ฐ€์ ธ์™€์„œ ์ €์žฅํ•ด์„œ

asset ํ˜•ํƒœ๋กœ ์ €์žฅํ•˜๊ณ  ํ™”๋ฉด์— ๋ฟŒ๋ ค์ค˜์•ผ ํ•œ๋‹ค.

 

identifier ์ƒ์ˆ˜์— 0๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ๋ถ€ํ„ฐ ๊ฐ€์ ธ์˜จ assetIdentifier๋ฅผ ์ €์žฅํ•˜๊ณ ,

์ „์—ญ๋ณ€์ˆ˜๋กœ ์ €์žฅ๋œ PHFetchResult<PHAsset> ํ˜•ํƒœ์˜ ๋ฐฐ์—ด์ธ fetchResults์— PHAsset.fetchAssets๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ identifier๋ฅผ ๋ณ€ํ™˜ํ•˜์—ฌ ๋„ฃ์–ด์ฃผ๊ณ  collectionView๋ฅผ reloadํ•˜๊ฒŒ ํ•œ ๋’ค, dismissํ•˜์—ฌ ๋ฉ”์ธ์œผ๋กœ ๋Œ์•„๊ฐ„๋‹ค.

 

ํ™”๋ฉด์— ๋ฟŒ๋ ค์ฃผ๋Š” ๋ถ€๋ถ„์€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ 

CollectionView์—์„œ ๊ผญ ๊ตฌํ˜„ํ•ด์•ผ ํ•˜๋Š” UICollectionViewDataSource ์•ˆ์˜ ๋‘๊ฐ€์ง€ ํ•จ์ˆ˜์—์„œ ์ง„ํ–‰ํ•œ๋‹ค.

 

extension ViewController: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return self.fetchResults?.count ?? 0
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PhotoCell", for: indexPath) as! PhotoCell
        
        if let asset = self.fetchResults?[indexPath.row]{
            cell.loadImage(asset: asset)
        }
        return cell
    }
}

 

์ฒซ๋ฒˆ์งธ ํ•จ์ˆ˜์—์„œ๋Š” fetchResults์˜ ๊ธธ์ด๋ฅผ ๊ณ„์‚ฐํ•˜์—ฌ ๋ช‡๊ฐœ์˜ ์…€์„ ๋ณด์—ฌ์ค„๊ฑด์ง€ ๊ฒฐ์ €ํ•˜๊ณ ,

 

๋‘๋ฒˆ์Ÿค ํ•จ์ˆ˜์—์„œ๋Š” asset์—์„œ ์š”์†Œ๋“ค์„ ๊ฐ€์ ธ์™€์„œ PhotoCell์— ์„ ์–ธํ•œ loadImageํ•จ์ˆ˜์— ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ณด๋‚ด ํ™”๋ฉด์— ๋ฟŒ๋ ค์ฃผ๊ฒŒ ํ•œ๋‹ค.

 

PhotoCell์˜ loadImage ํ•จ์ˆ˜๋Š”

 

func loadImage(asset: PHAsset){
        
        let imageManager = PHImageManager()
        
        let scale = UIScreen.main.scale
        let imageSize = CGSize(width: 150 * scale, height: 150 * scale)
        
        let options = PHImageRequestOptions()
        options.deliveryMode = .opportunistic
        
        self.photoImageView.image = nil
        
        imageManager.requestImage(for: asset, targetSize: imageSize, contentMode: .aspectFill, options: options, resultHandler: {
            image, info in
//            if (info?[PHImageResultIsDegradedKey] as? Bool) == true {
//                //์ €ํ™”์งˆ
//            }else{
//                //๊ณ ํ™”์งˆ
//            }
            self.photoImageView.image = image
        })
    }

 

์ด๋ ‡๊ฒŒ ์ƒ๊ฒผ๋‹ค.

PHImageManager๋ฅผ ์ธ์Šคํ„ด์Šคํ™” ํ•˜๊ณ , ์‚ฌ์ง„์˜ ์‚ฌ์ด์ฆˆ๋ฅผ ์„ค์ •ํ•˜๊ธฐ ์œ„ํ•œ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ์ž‘์—…๋“ค์„ ํ•œ๋‹ค,

imageManager.requestImage๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ, ์ „๋‹ฌ๋ฐ›์€ asset์„ ์‚ฌ์ด์ฆˆ, ํ™”์งˆ์— ๋งž๊ฒŒ ์„ค์ •์„ ํ•ด์ฃผ๊ณ 

์ด๋ฏธ์ง€๋ฅผ ๋ฐ˜ํ™˜ํ•˜์—ฌ ์ด๋ฏธ์ง€ ๋ทฐ์— ๋ฟŒ๋ฆฐ๋‹ค.

 

 

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋~~~~~~

 

 

LIST