๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐ŸŽ iOS

[Swift] PHPhotoLibraryChangeObserver ์™€ LimitedAccessAlert

by ํ‹ด๋”” 2022. 2. 10.
๋ฐ˜์‘ํ˜•

์ด์ „ ํฌ์ŠคํŒ…์— ์ด์–ด์„œ PHPhotoLibraryChnageObserver์— ๋Œ€ํ•ด์„œ ํฌ์ŠคํŒ… ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค~ 

 

PHPhotoLibraryChnageObserver๋ž€?

์ด๋ฆ„์—์„œ ์œ ์ถ”ํ•  ์ˆ˜ ์žˆ๋“ฏ์ด PH / Photo Library / Chnage Observer ์ž…๋‹ˆ๋‹ค. PhotoKit์—์„œ ์ œ๊ณตํ•˜๋Š” ํ”„๋กœํ† ์ฝœ๋กœ์„œ ์‚ฌ์šฉ์ž์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ณ€ํ™”๋ฅผ ๊ฐ์ง€ํ•˜์—ฌ ์•Œ๋ ค์ฃผ๋Š” ์—ญํ™œ์„ ํ•ฉ๋‹ˆ๋‹ค

์ด์ „ ํฌ์ŠคํŒ…์—์„œ ํ™•์ธํ•ด ๋ดค์„ ๋•Œ PHAsset์„ ์ด์šฉํ•ด์„œ ์‚ฌ์šฉ์ž ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ์žˆ๋Š” ์‚ฌ์ง„์ด๋‚˜ ๋™์˜์ƒ์„ ๊ฐ€์ง€๊ณ  ์˜ค๋ฉด ๋  ํ…๋ฐ ์™œ ์˜ต์ €๋ฒ„ ํ˜•ํƒœ๋กœ ํ”„๋กœํ† ์ฝœ์„ ์ œ๊ณตํ•ด ์ฃผ๋Š” ๊ฑธ๊นŒ์š”?

์šฐ์„  ์‚ฌ์šฉ์ž์˜ PHAsset์„ ๊ฐ€์ง€๊ณ  ์˜ค๋Š” ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด 

let allPhotos = PHAsset.fetchAssets(with: phFetchOptions)

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๊ฐ์ฒด๋“ค๊ณผ ์—ฐ๊ฒฐ๋œ ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ํ˜„์žฌ ์‹œ์ ์˜ ์‚ฌ์šฉ์ž ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ๊ฐ€์ง€๊ณ  ์˜ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. 

๋”ฐ๋ผ์„œ --> ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์ง„์„ ๋‹ค์šด๋กœ๋“œ ๋ฐ›๊ฑฐ๋‚˜ ์‚ฌ์ง„์„ ์ฐ๋Š” ๊ฒฝ์šฐ asset์„ ์—…๋ฐ์ดํŠธ ํ•ด์ค˜์•ผ ํ•˜๋Š”๋ฐ ๊ทธ ์—…๋ฐ์ดํŠธ ์‹œ์ ์„ ๋„ค์ดํ‹ฐ๋ธŒ ์•ฑ์—์„œ๋Š” ํ™•์ธ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ์—†๋Š” ๊ฒƒ์ด์ฃ ! ๊ทธ๋ž˜์„œ ์ด๋•Œ ํ•„์š”ํ•œ ๊ฒƒ์ด PHPhotoLibraryChangeObserver ์ž…๋‹ˆ๋‹ค.

 

์ฐธ๊ณ  :

๊ณต์‹ ๋ฌธ์„œ

 

์—ฌ๋Ÿฌ๊ฐ€์ง€ ์‚ฌ์šฉ์ž ์ผ€์ด์Šค๋ฅผ ์ƒ๊ฐํ•ด ๋ณผ ์ˆ˜ ์žˆ๋Š”๋ฐ ์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” ์ด ์„ธ ๊ฐ€์ง€ ์ผ€์ด์Šค์— ๋Œ€ํ•ด์„œ ์†Œ๊ฐœํ•ด ๋ณผ ๊นŒํ•ฉ๋‹ˆ๋‹ค

1. ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์ง„์„ ์ถ”๊ฐ€ํ•œ ๊ฒฝ์šฐ

-> ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์ง„์„ ์›น์—์„œ ๋‹ค์šด๋กœ๋“œ ๋ฐ›๊ฑฐ๋‚˜ ์‚ฌ์ง„์„ ์ฐ์€ ๊ฒฝ์šฐ ์ž…๋‹ˆ๋‹ค! ๋Œ€๋ถ€๋ถ„ ์ปค์Šคํ…€ ๊ฐค๋Ÿฌ๋ฆฌ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ์•ฑ์—์„œ๋Š” ์‚ฌ์šฉ์ž์—๊ฒŒ ์‚ฌ์ง„ ์ฐ๊ธฐ ๋ฒ„ํŠผ๋„ ํ•จ๊ป˜ ์ œ๊ณตํ•˜๊ณ  ์žˆ๋”๋ผ๊ตฌ์š”! (์‚ฌ์ง„ ์ฐ๋Š” ๊ฒƒ์€ ์•„์ฃผ ๋จผ ๋ฏธ๋ž˜์˜ ํฌ์ŠคํŒ…์—์„œ.. ๐Ÿฅฒ)

=> ์•ฑ์„ ์‹คํ–‰ํ–ˆ๋‹ค -> ์‚ฌ์ง„์„ ์ฒจ๋ถ€ํ•˜๋ ค๊ณ  ์•ฑ ๋‚ด๋ถ€์— ์žˆ๋Š” ์ปค์Šคํ…€ ๊ฐค๋Ÿฌ๋ฆฌ ํ™”๋ฉด์— ๋“ค์–ด ์™”๋‹ค -> ์›ํ•˜๋Š” ์‚ฌ์ง„์ด ์—†๋‹ค -> ์›น์—์„œ ๋‹ค์šด๋กœ๋“œ ๋ฐ›์•„์•ผ ๊ฒ ๋‹ค -> ์•ฑ์„ ์ข…๋ฃŒ ์•ˆํ•˜๊ณ  ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๋Œ๋ฆฌ๊ณ  ์›น์„ ์ผœ์„œ ๋‹ค์šด๋กœ๋“œ ๋ฐ›์Œ -> ์•ฑ ํ™”๋ฉด์œผ๋กœ ๋Œ์•„์˜ด -> ์—…๋ฐ์ดํŠธ๊ฐ€ ์•ˆ๋œ๋‹ค๋ฉด? ๋‹ค์šด๋กœ๋“œ ํ•œ ์‚ฌ์ง„์ด ์•ˆ๋ณด์ž„

์œ„ ์ผ€์ด์Šค์— ๋Œ€์‘ํ•ด ๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค~ ์ด๊ฒƒ๊ณผ ๋”๋ถˆ์–ด์„œ ๋น„์Šทํ•œ ์ผ€์ด์Šค๋กœ 

2. ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์ง„์„ ์‚ญ์ œํ•œ ๊ฒฝ์šฐ

์ž…๋‹ˆ๋‹ค. ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์•ฑ์„ ์ข…๋ฃŒ ์•ˆํ•˜๊ณ  ์‚ฌ์šฉ์ž ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ˆ˜์ •ํ•˜๋Š” ๊ฒฝ์šฐ์— ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ œ์—์š”! ์ปค์Šคํ…€ ๊ฐค๋Ÿฌ๋ฆฌ ๊ฐ์ฒด๋ฅผ ๋‹ค์‹œ ์ƒ์„ฑํ•˜์—ฌ ํ™”๋ฉด์— ์˜ฌ๋ ค ์ค€๋‹ค๋ฉด ๋ฌธ์ œ๊ฐ€ ์—†๊ฒ ์ง€๋งŒ ์ปค์Šคํ…€ ๊ฐค๋Ÿฌ๋ฆฌ๊ฐ€ ์•ฑ ํ™”๋ฉด์— ๊ทธ๋Œ€๋กœ ์˜ฌ๋ผ์™€ ์žˆ๊ณ  ๋ฐฑ๊ทธ๋ผ์šด๋“œ์— ๊น”์•„๋†จ๋‹ค๊ฐ€ -> ์‚ฌ์šฉ์ž ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ๋“ค์–ด๊ฐ€์„œ ์‚ฌ์ง„์„ ์‚ญ์ œํ•˜๊ฒŒ ๋˜๋ฉด ์ปค์Šคํ…€ ๊ฐค๋Ÿฌ๋ฆฌ์—๋Š” ๋ฐ˜์˜์ด ์•ˆ๋˜์–ด ์žˆ๊ฒŒ ๋˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค! ใ… ใ…  

Anyway! ์‚ฌ์šฉ์ž ์ธก๋ฉด์„ ๊ณ ๋ คํ•˜๋Š”๊ฒŒ ๋„ค์ดํ‹ฐ๋ธŒ ๊ฐœ๋ฐœ์ž์˜ ์ˆ™๋ช…! 

3. PHAuthorizationStatus๊ฐ€ .limited์ธ ๊ฒฝ์šฐ

์ด์ „ ํฌ์ŠคํŒ…์„ ๋ณด์…จ๋‹ค๋ฉด ์•Œ๊ณ  ๊ณ„์‹œ๊ฒ ์ง€๋งŒ ์ด๋ฒˆ ํฌ์ŠคํŒ…์ด ์ฒ˜์Œ์ด์‹ค ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ! 

iOS 14+ ์—์„œ๋Š” ์‚ฌ์ง„ ๊ถŒํ•œ์ด ์ด์ „ ๋ฒ„์ „๊ณผ ๋‹ค๋ฅด๊ฒŒ ํ•œ๊ฐ€์ง€ ๋” ์ œ๊ณตํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ”๋กœ select photo ์ธ๋ฐ์š”! ์‚ฌ์šฉ์ž๊ฐ€ ์„ ํƒํ•œ ์‚ฌ์ง„๋งŒ ์ ‘๊ทผ ๊ถŒํ•œ์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค! ์‚ฌ์šฉ์ž ์ธก๋ฉด์—์„œ๋Š” ๋” ๊ฐ•ํ™”๋œ ๋ณด์•ˆ์„ ๊ฒฝํ—˜ํ•  ์ˆ˜ ์žˆ(...?)๋‹ค๊ณ  ํ•ด์š”! ์ ์  ๊ฐ•๋ ฅํ•ด์ง€๋Š” ๋ณด์•ˆ์— ๊ฐœ๋ฐœ์ž๋Š” ๋ˆˆ๋ฌผ์„ ํ›”์ณ๋ด…๋‹ˆ๋‹ค..

์ด๊ฒƒ์„ ์™œ ๊ณ ๋ คํ•ด ์ฃผ์–ด์•ผ ํ•˜๋Š๋ƒ๋ฉด ์•ฑ ์ƒ๋ช…์ฃผ๊ธฐ(App life cycle, UIViewController ๋ผ์ดํ”„์‚ฌ์ดํด ์•„๋‹˜) ๋‹น ํ•œ๋ฒˆ์”ฉ limited ๊ถŒํ•œ์ผ ๋•Œ, ์ƒˆ๋กœ์šด ์‚ฌ์ง„์„ ๋” ์„ ํƒํ•˜๊ฒ ๋ƒ๋Š” Alert View๊ฐ€ default๋กœ ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค!!

๋ฌผ๋ก  info.plist ์—์„œ ์„ค์ •์„ ๋ฐ”๊พธ์–ด ์ฃผ๋ฉด Alert View๊ฐ€ ๋œจ์ง€ ์•Š๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ œ ๊ฐœ์ธ์ ์ธ ์ƒ๊ฐ์œผ๋กœ๋Š” ์ด๊ฒƒ์„ ๋ง‰๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ ๊ตฌํ˜„ํ•ด ์ฃผ์–ด์•ผ ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•ด์š”. ์ฒ˜์Œ ๊ถŒํ•œ ์„ค์ •์‹œ ์„ ํƒํ•œ ์ด๋ฏธ์ง€๋งŒ ๋œจ๊ฒŒ ๋˜๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ์ƒˆ๋กญ๊ฒŒ ์ฐ์€ ์‚ฌ์ง„์„ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ ์‚ฌ์šฉ์ž ์ธก๋ฉด์—์„œ ์ถฉ๋ถ„ํžˆ ๋ฒˆ๊ฑฐ๋กœ์šด ์ž‘์—…์ด ๋  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค! ๋”ฐ๋ผ์„œ .limited status์—์„œ picker view๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์ง„์„ ๋” ์„ ํƒํ•œ ๊ฒฝ์šฐ ์–ด๋–ป๊ฒŒ ๋ฐ˜์˜ํ•  ์ˆ˜ ์žˆ์„ ์ง€ ์ƒ๊ฐํ•ด ๋ด…์‹œ๋‹ค

 


์ฝ”๋”ฉ ํƒ€์ž„ ์‹œ์ž‘!!


1. PHPhotoLibrary

์šฐ์„  viewDidLoad์™€ deinit์— ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด ์ฃผ์„ธ์š”

    override func viewDidLoad() {
        super.viewDidLoad()
        
        PHPhotoLibrary.shared().register(self)
    }
    
    deinit {
        PHPhotoLibrary.shared().unregisterChangeObserver(self)
    }

PHPhotoLibrary๋Š” ์‚ฌ์šฉ์ž ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ๋Œ€ํ•œ ์ ‘๊ทผ๊ณผ ๋ณ€๊ฒฝ์„ ๊ด€๋ฆฌํ•˜๋Š” ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž ํฌํ†  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๋ณ€ํ™”๋ฅผ ๊ฐ์ง€ํ•˜๊ธฐ ์œ„ํ•ด์„œ register(_:) ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ observer ๊ฐ์ฒด๋ฅผ ์ง€์ •ํ•ด ์ค๋‹ˆ๋‹ค

deinit์—์„œ unregisterChangeObserver(_:)์„ ์ด์šฉํ•ด์„œ ์˜ต์ €๋น™์„ ํ•ด์ œํ•ด ์ค๋‹ˆ๋‹ค. 

 

2. PHPhotoLibraryChnageObserver

extension GallaryViewController: PHPhotoLibraryChangeObserver {
    func photoLibraryDidChange(_ changeInstance: PHChange) {
        print("\(#function), \(#line)")
    }
}

PHPhotoLibraryChangeObserver๋ฅผ ์ฑ„ํƒํ•ด ์ฃผ์„ธ์š”!

์ด๋•Œ ๊ตฌํ˜„ํ•ด์•ผ ํ•˜๋Š” ๋ฉ”์†Œ๋“œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ตฌํ˜„์ด ์•ˆ๋˜์—ˆ๋‹ค๋ฉด ์—๋Ÿฌ๋ฉ”์‹œ์ง€๊ฐ€ ๋œจ๋‹ˆ ์ž๋™ ์™„์„ฑ์œผ๋กœ ์œ„ ํ”„๋กœํ† ์ฝœ์„ ๋”ฐ๋ฅด๋„๋ก ํ•ฉ์‹œ๋‹น

photoLibraryDidChange(_:) ๋Š” ํฌํ†  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๋ณ€๊ฒฝ์ด ๋ฐœ์ƒ๋˜์—ˆ์Œ์„ observer์—๊ฒŒ ์•Œ๋ฆฌ๋Š” ์ธ์Šคํ„ด์Šค ๋ฉ”์†Œ๋“œ ์ž…๋‹ˆ๋‹ค.

์‚ฌ์šฉ์ž ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ๋ณ€๊ฒฝ์ด ๋ฐœ์ƒํ•˜๋ฉด ์ด ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋˜๊ณ  ์—ฌ๊ธฐ์— PHChnage๋กœ album๊ณผ collections ์˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด์š”

 

์—ฌ๊ธฐ๊นŒ์ง€ ํ•ด ๋†“์œผ์‹œ๊ณ  ์•ฑ์„ ์‹คํ–‰ ์‹œ์ผœ ์ปค์Šคํ…€ ๊ฐค๋Ÿฌ๋ฆฌ ํ™”๋ฉด์ธ

GallaryViewController๋กœ ์™€์ค๋‹ˆ๋‹ค. 

์ด ํ™”๋ฉด์—์„œ ์›น์œผ๋กœ ๊ฐ€์„œ ์ด๋ฏธ์ง€๋ฅผ ๋‹ค์šด๋กœ๋“œ ๋ฐ›์€ ๋’ค ๋‹ค์‹œ ์•ฑ ์ปค์Šคํ…€ ๊ฐค๋Ÿฌ๋ฆฌ ํ™”๋ฉด์œผ๋กœ ๋„˜์–ด์˜ค๋ฉด ์ด๋ฏธ์ง€๊ฐ€ ์—…๋ฐ์ดํŠธ ์•ˆ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์ฝ˜์†”์— ์ฐํžˆ๋Š” ๋กœ๊ทธ๋ฅผ ๋ณด๋ฉด photoLibraryDidChange(_:) ๊ฐ€ ํ˜ธ์ถœ๋˜๋Š” ๊ฒƒ์„ ๋ณด์‹ค ์ˆ˜ ์žˆ์–ด์š” ๐Ÿ™Œ๐Ÿป

์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•ด ๋ด…์‹œ๋‹ค

    func photoLibraryDidChange(_ changeInstance: PHChange) {
    	// 1
        guard let changes = changeInstance.changeDetails(for: self.asset) else {
            return
        }
        
        // 2
        let beforeAsset = changes.fetchResultBeforeChanges
        let afterAsset = changes.fetchResultAfterChanges
        
        // 3
        print("๋ณ€๊ฒฝ ์ „ ์ฒซ๋ฒˆ์งธ ์—์…‹ : ", beforeAsset.firstObject?.localIdentifier ?? "nil")
        print("๋ณ€๊ฒฝ ํ›„ ์ฒซ๋ฒˆ์งธ ์—์…‹ : ", afterAsset.firstObject?.localIdentifier ?? "nil")
    }

PHChange๋ฅผ ์ด์šฉํ•ด์„œ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์•Œ ์ˆ˜ ์žˆ์–ด์š” changeInstance๋ผ๋Š” ๋ณ€์ˆ˜๋กœ ๋‹ด๊ฒจ ์˜ค๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ ์ด์šฉํ•ด์„œ ์‚ฌ์šฉํ•ด ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

 

1. self.asset์€ ๋ณ€๊ฒฝ๋˜๊ธฐ ์ „์— ์ปค์Šคํ…€ ๊ฐค๋Ÿฌ๋ฆฌ ํ™”๋ฉด์„ ๊ทธ๋ ค์ฃผ๊ธฐ ์œ„ํ•ด ๋ฐ›์€ asset ์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ๊ฐค๋Ÿฌ๋ฆฌ์— ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ์ƒ๊ธฐ๊ธฐ ์ด์ „์— ๊ฐ€์ง€๊ณ  ์˜จ ์—์…‹์ž…๋‹ˆ๋‹ค! ์ด๋ฅผ changeInstance์™€ ๋น„๊ตํ•˜๊ธฐ ์œ„ํ•ด changeDetails(for:)์„ ์ด์šฉํ•˜์—ฌ ๋น„๊ตํ•ด ์ค„ ์ˆ˜ ์žˆ์–ด์š”!!

์ด๋•Œ ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ์—†๊ณ  ์ด์ „ asset์ธ self.asset๊ณผ ๊ฐ™๋‹ค๋ฉด nil์„ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ๋•Œ๋ฌธ์—(๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ์—†์–ด์„œ)  guard let ์„ ์‚ฌ์šฉํ•ด์„œ ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ์žˆ๋Š” ๊ฒฝ์šฐ์—๋งŒ ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ํƒˆ ์ˆ˜ ์žˆ๋„๋ก ์ž‘์„ฑํ•ด ์ค์‹œ๋‹ค!

 

์ด๋•Œ self.asset์„ ๋„ฃ์–ด ์ฃผ์—ˆ์ง€๋งŒ PHAsset ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์•จ๋ฒ”์˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด PHFetchResult<PHAssetCollection> ๊ฐ์ฒด๋ฅผ ๋„ฃ์–ด์ค„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ˜ํ™˜๊ฐ’์€ ํ˜„์žฌ PHFetchResult<PHAsset>์™€ ๋น„๊ตํ•˜๊ธฐ ๋•Œ๋ฌธ์— PHFetchResult<PHAsset>์œผ๋กœ ๋™์ผํ•ฉ๋‹ˆ๋‹ค!

 

2. ํ”„๋กœํผํ‹ฐ ์ด๋ฆ„์—์„œ ์•Œ ์ˆ˜ ์žˆ๋“ฏ์ด fetchResultBeforeChanges๋Š” ๋ณ€๊ฒฝ๋˜๊ธฐ ์ด์ „์˜ asset์— ๋Œ€ํ•œ ์ •๋ณด๊ฐ€ ๋‹ด๊ฒจ ์žˆ๊ณ , fetchResultAfterChanges๋Š” ๋ณ€๊ฒฝ ํ›„์— ๋Œ€ํ•œ asset ์ •๋ณด๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค

 

3. ๋กœ๊ทธ๋ฅผ ์ฐ์–ด๋ณด๊ธฐ ์œ„ํ•ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์‹œ๊ณ 

 

๋‹ค์‹œ ์•ฑ์„ ์‹คํ–‰ํ•˜์—ฌ ์ปค์Šคํ…€ ๊ฐค๋Ÿฌ๋ฆฌ๋กœ ์ด๋™ํ•œ๋’ค ์‚ฌํŒŒ๋ฆฌ๋กœ ๊ฐ€์„œ ์‚ฌ์ง„์„ ๋‹ค์šด๋กœ๋“œ ๋ฐ›์•„ ์ค๋‹ˆ๋‹ค. 

๋ณ€๊ฒฝ ์ „ ์ฒซ๋ฒˆ์งธ ์—์…‹ :  0729C739-917A-4C3C-B40D-AC9B1BC898E4/L0/001
๋ณ€๊ฒฝ ํ›„ ์ฒซ๋ฒˆ์งธ ์—์…‹ :  D5AA1370-50BE-455F-B3DC-A0E1C980C167/L0/001

์‚ฌ์šฉ์ž ๊ฐค๋Ÿฌ๋ฆฌ์— ๋ณ€๊ฒฝ์ด ์ผ์–ด๋‚ฌ๊ธฐ ๋•Œ๋ฌธ์— photoLibraryDidChange(_:)๊ฐ€ ํ˜ธ์ถœ๋˜๊ณ  ์œ„์™€ ๊ฐ™์ด ๋กœ๊ทธ๊ฐ€ ์ฐํžˆ๋Š” ๊ฒƒ์„ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ์–ด์š”!

์„ฑ๊ณต์ ์œผ๋กœ ๊ฐค๋Ÿฌ๋ฆฌ์— ๋Œ€ํ•œ ๋ณ€๊ฒฝ์„ ๊ฐ€์ง€๊ณ  ์˜จ ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์–ด์š”! ์ด์ œ ํ™”๋ฉด ์—…๋ฐ์ดํŠธ์— ๊ด€ํ•œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด ๋ด…์‹œ๋‹ค

 

    func photoLibraryDidChange(_ changeInstance: PHChange) {
        guard let changes = changeInstance.changeDetails(for: self.asset) else {
            return
        }
        
        let beforeAsset = changes.fetchResultBeforeChanges
        let afterAsset = changes.fetchResultAfterChanges
        
        print("๋ณ€๊ฒฝ ์ „ ์ฒซ๋ฒˆ์งธ ์—์…‹ : ", beforeAsset.firstObject?.localIdentifier ?? "nil")
        print("๋ณ€๊ฒฝ ํ›„ ์ฒซ๋ฒˆ์งธ ์—์…‹ : ", afterAsset.firstObject?.localIdentifier ?? "nil")
        
        DispatchQueue.main.async {
            self.asset = afterAsset
            self.collectionView.reloadData()
        }
    }

๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ UI๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ฃผ์–ด์•ผ ํ•ด์š”!

 

์ƒˆ๋กœ์šด ์ด๋ฏธ์ง€๋ฅผ ๋‹ค์šด๋กœ๋“œ ๋ฐ›์€ ๋’ค ์•ฑ์œผ๋กœ ๋Œ์•„์˜ค๋ฉด ์ƒˆ๋กœ ๋‹ค์šด๋กœ๋“œ ๋ฐ›์€ ์‚ฌ์ง„์„ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•ด collection view ๊ฐ€ reloadํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ์–ด์š”

ํ•ด๊ฒฐ๋˜์—ˆ์œผ๋‹ˆ ์—ฌ๊ธฐ์„œ ๋๋‚ด๊ณ  ์‹ถ์ง€๋งŒ ์ƒ๊ฐํ•ด๋ณผ ๋งŒํ•œ ๊ฒƒ์ด ๋‚จ์•„ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ”๋กœ ์ด๋ฏธ์ง€๊ฐ€ ํ•œ๊ฐœ ํ˜น์€ ์—ฌ๋Ÿฌ๊ฐœ๊ฐ€ ์ƒˆ๋กญ๊ฒŒ ์ถ”๊ฐ€๋˜๋Š”๋ฐ์— ๋Œ€ํ•ด์„œ collection view ์ „์ฒด์— ๋Œ€ํ•ด reload๋ฅผ ์‹œ์ผœ์ฃผ๊ณ  ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์‚ฌ์šฉ์ž ์ธก๋ฉด์—์„œ ๋ฐ์ดํ„ฐ ๊ฐฑ์‹ ์„ ์ œ๊ณตํ•˜๋”๋ผ๋„ ์ข€ ๋” ํšจ์œจ์ ์ด๊ฒŒ ์ œ๊ณตํ•˜๋ฉด ์ข‹๊ฒ ์ฃ ?

๊ทธ๋ ‡๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ์›ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋งŒ ๋ณ€๊ฒฝํ•ด ์ค„ ์ˆ˜ ์žˆ์„๊นŒ์š”?

 

collectionView์—์„œ ์›ํ•˜๋Š” cell ๋งŒ ์—…๋ฐ์ดํŠธ ํ•ด ์ค„ ์ˆ˜ ์žˆ์œผ๋ฉฐ PHChange๋ฅผ ํ†ตํ•ด์„œ ์ถ”๊ฐ€๋˜๊ฑฐ๋‚˜ ์‚ญ์ œ, ๋ณ€๊ฒฝ๋œ ์˜ค๋ธŒ์ ํŠธ์˜ ์ธ๋ฑ์Šค๋ฅผ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

์ฝ”๋“œ๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ์ˆ˜์ •ํ•ด ๋ณผ๊นŒ์š”?

Apple ๊ณต์‹ ๋ฌธ์„œ์—์„œ ์ œ์•ˆํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ฐธ๊ณ ํ–ˆ์Šต๋‹ˆ๋‹ค

extension GallaryViewController: PHPhotoLibraryChangeObserver {
    func photoLibraryDidChange(_ changeInstance: PHChange) {
        guard let changes = changeInstance.changeDetails(for: self.asset) else {
            return
        }
        // asset update 
        self.asset = changes.fetchResultAfterChanges
        
        // 1
        if changes.hasIncrementalChanges {
            DispatchQueue.main.async {
                // 2
                self.collectionView.performBatchUpdates {
                    // 3
                    if let inserted = changes.insertedIndexes, !inserted.isEmpty {
                        // 4
                        self.collectionView.insertItems(at: inserted.map({ IndexPath(item: $0, section: 0) }))
                    }
                }
            }
        }
        
    }
}

 

๊ธฐ์กด asset์„ ์ƒˆ๋กญ๊ฒŒ fetch๋œ asset์œผ๋กœ ๋ณ€๊ฒฝํ•ด ์ฃผ์…”์•ผ ํ•ฉ๋‹ˆ๋‹ค! ์žŠ์œผ์‹œ๋ฉด ์—…๋ฐ์ดํŠธ๊ฐ€ ์ œ๋Œ€๋กœ ๋˜์ง€ ์•Š์„ ๊ฑฐ์—์š”!

 

1. hasIncrementalChanges๋Š” boolean ํƒ€์ž…์˜ ์ธ์Šคํ„ด์Šค ํ”„๋กœํผํ‹ฐ์ž…๋‹ˆ๋‹ค. fetchResult๊ฐ€ added, removed, updated ๋˜์—ˆ๋‹ค๋ฉด true ๊ฐ’์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ๊ฐ insertedIndexes, removedIndexes, changedIndexes๋ฅผ ํ†ตํ•ด์„œ index set์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋ฏธ์ง€๋Š” ์—ฌ๋Ÿฌ๊ฐœ ์ถ”๊ฐ€๋˜๊ณ  ์‚ญ์ œ๋  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— indexPath๋กœ ์ œ๊ณต๋˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ ์—ฌ๋Ÿฌ๊ฐœ์˜ index๋ฅผ ์ œ๊ณตํ•˜๋Š” set๋กœ ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค.

 

2. collectionView๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๊ธฐ ์œ„ํ•ด์„œ performBatchUpdates์˜ updates ํด๋กœ์ €๋ฅผ ์ž‘์„ฑํ•ด ์ค๋‹ˆ๋‹ค.

 

3. ์ƒˆ๋กญ๊ฒŒ ์ถ”๊ฐ€๋œ ์ด๋ฏธ์ง€์˜ ์ธ๋ฑ์Šค๋Š” insertIndexes๋กœ ์–ป์„ ์ˆ˜ ์žˆ์–ด์š”! ์ด๋•Œ ์ƒˆ๋กญ๊ฒŒ ์ถ”๊ฐ€๋œ ์ด๋ฏธ์ง€๋Š” ์—†๊ณ  ์‚ญ์ œ๋œ ์ด๋ฏธ์ง€๊ฐ€ ์กด์žฌํ•  ๊ฒฝ์šฐ hasIncrementalChanges๋Š” true ์ด๊ณ  insertIndexes๋Š” empty ์ด๊ธฐ ๋•Œ๋ฌธ์— isEmpty๋กœ ์ถ”๊ฐ€๋œ ์ธ๋ฑ์Šค๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•ด ์ค๋‹ˆ๋‹ค

 

4. insertItem์„ ์ด์šฉํ•ด์„œ ์ƒˆ๋กœ์šด ์ด๋ฏธ์ง€๋ฅผ ๋ณด์ด๋„๋ก ํ•ด์ค๋‹ˆ๋‹ค.

 

์‚ญ์ œ๋„ ๋™์ผํ•˜๊ฒŒ ์ž‘์„ฑํ•ด ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด ๋ด…์‹œ๋‹ค

 

extension GallaryViewController: PHPhotoLibraryChangeObserver {
    func photoLibraryDidChange(_ changeInstance: PHChange) {
        guard let changes = changeInstance.changeDetails(for: self.asset) else {
            return
        }
        
        self.asset = changes.fetchResultAfterChanges
        
        if changes.hasIncrementalChanges {
            DispatchQueue.main.async {
                self.collectionView.performBatchUpdates {
                    if let inserted = changes.insertedIndexes, !inserted.isEmpty {
                        self.collectionView.insertItems(at: inserted.map({ IndexPath(item: $0, section: 0) }))
                    }
                    
                    if let removed = changes.removedIndexes, !removed.isEmpty {
                        self.collectionView.deleteItems(at: removed.map({ IndexPath(item: $0, section: 0) }))
                    }
                }
            }
        }
        
    }
}

 

์ปค์Šคํ…€ ๊ฐค๋Ÿฌ๋ฆฌ ํ™”๋ฉด๊นŒ์ง€ ๋กœ๋“œํ•œ ํ›„ ์‚ฌ์šฉ์ž ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ์‚ฌ์ง„์„ ์‚ญ์ œํ•ด ์ค€ ๋’ค ๋‹ค์‹œ ์•ฑ ํ™”๋ฉด์œผ๋กœ ๋Œ์•„์˜ค๋ฉด ์—…๋ฐ์ดํŠธ๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ์ด๋ฃจ์–ด์ง€๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค~~๐Ÿ™Œ๐Ÿป

 

์—ฌ๊ธฐ์„œ ๋! ํ•˜๊ณ  ์‹ถ์ง€๋งŒ ํ•œ๊ฐ€์ง€ ๋” ๋‚จ์•„ ์žˆ์Šต๋‹ˆ๋‹ค.

 

3. .limited ๋Œ€์‘ํ•˜๊ธฐ

์•ฑ์„ ์ œ๊ฑฐํ•˜๊ณ  ๋‹ค์‹œ ๋นŒ๋“œ ๋ฐ ์‹คํ–‰ ํ•ด์ฃผ์…”๋„ ๋ฉ๋‹ˆ๋‹ค! ๊ทธ ํ›„์— ์ด๋ฏธ์ง€ ์ ‘๊ทผ ๊ถŒํ•œ์„ .limited ๊ฐ€ ๋˜๋„๋ก select photos๋ฅผ ์„ ํƒํ•˜์‹œ๋ฉด ๋˜์š”! ์•„๋‹ˆ๋ฉด ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ ํ˜น์€ ๊ธฐ๊ธฐ์˜ settings์— ๋“ค์–ด๊ฐ€์…”์„œ ๊ถŒํ•œ์„ ๋ณ€๊ฒฝํ•ด ์ฃผ์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

 

์ด๋ ‡๊ฒŒ ๊ถŒํ•œ์ด All photos๋กœ ๋˜์–ด ์žˆ์„ ํ…๋ฐ์š”, Selected photos ๋กœ ๋ณ€๊ฒฝํ•ด ์ค๋‹ˆ๋‹ค. 

 

Selected Photos๋ฅผ ๋ˆ„๋ฅด๋ฉด ์‹œ์Šคํ…œ picker view์—์„œ ์ด๋ฏธ์ง€๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š” ์ฐฝ์ด ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค. ๋ช‡ ๊ฐœ์˜ ์ด๋ฏธ์ง€๋ฅผ ์„ ํƒํ•˜์‹œ๊ณ  done์„ ๋ˆŒ๋Ÿฌ ์ฃผ์„ธ์š”

๋‹ค์‹œ ์•ฑ ํ™”๋ฉด์œผ๋กœ ๋Œ์•„์˜ค๋ฉด ์•ž์„œ ๋ง์”€ ๋“œ๋ ธ๋˜ '์‚ฌ์ง„์„ ๋” ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š”' ์–ผ๋Ÿฟ๋ทฐ๊ฐ€ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค! ์—ฌ๊ธฐ์„œ Select More Photos๋ฅผ ์„ ํƒํ•˜๋”๋ผ๋„ ๊ถŒํ•œ์„ ๋ถ€์—ฌ๋ฐ›์€ ์„ ํƒํ•œ ์ด๋ฏธ์ง€๊ฐ€ ๋ฐ”๋กœ ์—…๋ฐ์ดํŠธ ์•ˆ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด์š”!

via GIPHY

์ด ์–ผ๋Ÿฟ๋ทฐ๋Š” view ๋ผ์ดํ”„ ์‚ฌ์ดํด์ด ์•„๋‹Œ ์•ฑ์˜ ์ƒ๋ช… ์ฃผ๊ธฐ๋ฅผ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค! ๋งŒ์•ฝ ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์ง„์„ ๋” ์„ ํƒํ•˜๋Š” ๊ฒƒ์„ ๋ง‰์•„๋ฒ„๋ฆฌ๊ณ  ์‹ถ๋‹ค๋ฉด ์ด ์–ผ๋Ÿฟ๋ทฐ๋ฅผ ๋„์šฐ์ง€ ์•Š์œผ๋ฉด ๋ฉ๋‹ˆ๋‹ค! ๋‹คํ–‰์ด๋„ ์ด ์˜ต์…˜์„ ์ œ๊ณตํ•ด ์ฃผ๊ณ  ์žˆ๋Š”๋ฐ์š”! info.plist๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค

์•„๋ฌด ํ•„๋“œ๋‚˜ ์„ ํƒํ•ด์„œ + ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด์‹œ๋ฉด ์ƒˆ๋กœ์šด ํ•„๋“œ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค! ์ƒˆ๋กœ์šด ํ•„๋“œ์— ์•„๋ž˜ key๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”

PHPhotoLibraryPreventAutomaticLimitedAccessAlert

๊ทธ๋Ÿฌ๋ฉด ์œ„ ์‚ฌ์ง„ ์ฒ˜๋Ÿผ Prevent limited photos access alert๋กœ ๋ณ€ํ•  ๊ฑฐ์—์š”! ์ด๋•Œ ๊ฐ’์„ NO๋กœ ํ•œ๋‹ค๋ฉด limited photos accest ์–ผ๋Ÿฟ ์ฐฝ์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๊ฒ ๋‹ค๋Š” ๊ฒƒ์ด๊ณ  YES๋ฅผ ํ•ด์ฃผ๋ฉด  Prevent ํ•ด์ฃผ๊ฒ ๋‹ค! ์ž…๋‹ˆ๋‹ค!!

์ด์ œ YES๋กœ ๋ฐ”๊ฟ”์ฃผ๊ณ  ๋‹ค์‹œ ์•ฑ์„ ์‹คํ–‰ํ•˜๋ฉด ์–ผ๋Ÿฟ์ฐฝ์ด ์•ˆ๋œจ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด์š”!

๊ทธ๋Ÿฐ๋ฐ ์ด๋•Œ! ์‚ฌ์šฉ์ž๊ฐ€ limited ์˜ต์…˜์„ ์„ ํƒํ–ˆ๋Š”๋ฐ ์ƒˆ๋กญ๊ฒŒ ์ฐ์€ ์‚ฌ์ง„์„ ์•ฑ ๋‚ด๋ถ€์— ์žˆ๋Š” ์ปค์Šคํ…€ ๊ฐค๋Ÿฌ๋ฆฌ์— ๋œจ๊ฒŒ ํ•˜๊ณ  ์‹ถ๋‹ค! ๊ทธ๋ ‡๋‹ค๋ฉด ๋‹ค์‹œ ์œ„ ๊ฐ’์„ NO๋กœ ๋ฐ”๊พธ์–ด์ฃผ์‹œ๊ณ  ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด ์ค์‹œ๋‹ค.

 

func photoLibraryDidChange(_ changeInstance: PHChange) {
        guard let changes = changeInstance.changeDetails(for: self.asset) else {
            return
        }
        
        self.asset = changes.fetchResultAfterChanges
        DispatchQueue.main.async {
            if changes.hasIncrementalChanges {
                
                self.collectionView.performBatchUpdates {
                    if let inserted = changes.insertedIndexes, !inserted.isEmpty {
                        self.collectionView.insertItems(at: inserted.map({ IndexPath(item: $0, section: 0) }))
                    }
                    
                    if let removed = changes.removedIndexes, !removed.isEmpty {
                        self.collectionView.deleteItems(at: removed.map({ IndexPath(item: $0, section: 0) }))
                    }
                }
                
            } else {
                self.collectionView.reloadData()
            }
        }
    }

Select more photos๋ฅผ ์„ ํƒํ•ด์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์‚ฌ์ง„์„ ์ถ”๊ฐ€ํ•œ ๊ฒฝ์šฐ hasIncrementalChanges๋กœ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ถ”์ ํ•  ์ˆ˜๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค... ๐Ÿฅฒ 

๋”ฐ๋ผ์„œ else๋ฅผ ์ด์šฉํ•ด์„œ collectionView๋ฅผ ๋‹ค์‹œ ๋ฆฌ๋กœ๋“œ ํ•ด์ค๋‹ˆ๋‹ค. ์ด๋•Œ main ์Šค๋ ˆ๋“œ์—์„œ ๋Œ์•„์•ผ ํ•˜๋ฏ€๋กœ DispatchQueue์˜ ์œ„์น˜์— ์ฃผ์˜ํ•˜์„ธ์š”! ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ์‹คํ–‰ํ•ด ์ค์‹œ๋‹ค

์œ„ ์–ผ๋Ÿฟ ์ฐฝ์—์„œ Select More Photos๋ฅผ ๋ˆŒ๋Ÿฌ์ฃผ๊ณ  ์ƒˆ๋กœ์šด ์ด๋ฏธ์ง€๋ฅผ ๋ช‡ ๊ฐœ ์„ ํƒํ•ด ์ค์‹œ๋‹ค! ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ์—์„œ๋Š” ํ”ฝ์ปค ๋ทฐ๊ฐ€ ์ข€ ๋Š๋ฆฌ๊ฒŒ ๋œจ๋Š”๋ฐ์šฉ ๋ช‡ ์ดˆ๋งŒ ๊ธฐ๋‹ค๋ ค ์ฃผ๋ฉด ๋œฐ๊ฑฐ์—์š”! ์‚ฌ์ง„์„ ์„ ํƒํ•˜๊ณ  Done์„ ๋ˆŒ๋Ÿฌ ๊ถŒํ•œ ์ž‘์—…์„ ๋งˆ์น˜๋ฉด ์ปค์Šคํ…€ ๊ฐค๋Ÿฌ๋ฆฌ์˜ ์‚ฌ์ง„์ด ์—…๋ฐ์ดํŠธ ๋œ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด์š”!!


์ด๋ ‡๊ฒŒ ์‚ฌ์šฉ์ž ์•จ๋ฒ” ๋ณ€๊ฒฝ ์‚ฌํ•ญ์— ๋Œ€ํ•ด ๋Œ€์‘์„ ํ•ด๋ดค๋Š”๋ฐ์š”, ์‚ฌ์‹ค ๋” ๋งŽ์€ ์ผ€์ด์Šค๊ฐ€ ์กด์žฌํ•  ๊ฑฐ๋ผ๊ณ  ์ƒ๊ฐํ•˜๊ณ  ์ด ๋ฐฉ๋ฒ•๋ณด๋‹ค ๋” ์ข‹์€ ๋ฐฉ๋ฒ•์ด ์žˆ์„ ๊ฑฐ๋ผ๊ณ  ์ƒ๊ฐํ•ด์š”! ํ˜น์‹œ ์•Œ๊ณ  ๊ณ„์‹  ๋ถ„์ด ์žˆ๋‹ค๋ฉด ๋Œ“๊ธ€์ด๋‚˜ ๋งํฌ๋กœ ๊ณต์œ ํ•ด์ฃผ์‹œ๋ฉด ๊ฐ์‚ฌํ•  ๊ฒƒ ๊ฐ™์•„์š”!

ํ‹€๋ฆฌ๊ฑฐ๋‚˜ ์ด์ƒํ•œ ๊ฒƒ, ๋ฌธ์ œ๋˜๋Š” ๊ฒƒ์ด ์žˆ๋‹ค๋ฉด ๋Œ“๊ธ€๋กœ ์•Œ๋ ค์ฃผ์‹œ๋ฉด ๊ฐ์‚ฌํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค!!

 


์ฐธ๊ณ  ๋„์„œ ๋ฐ ์‚ฌ์ดํŠธ

https://swiftsenpai.com/development/photo-library-permission/

 

728x90
๋ฐ˜์‘ํ˜•

๋Œ“๊ธ€