์ด์ ํฌ์คํ ์ ์ด์ด์ 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๋ฅผ ์ ํํ๋๋ผ๋ ๊ถํ์ ๋ถ์ฌ๋ฐ์ ์ ํํ ์ด๋ฏธ์ง๊ฐ ๋ฐ๋ก ์ ๋ฐ์ดํธ ์๋๋ ๊ฒ์ ํ์ธํ ์ ์์ด์!
์ด ์ผ๋ฟ๋ทฐ๋ 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/
๋๊ธ