티스토리 뷰

반응형

 

안녕하세요 :) Zedd입니다.

벌써 추석이네요!! 즐거운 추석 보내세요 ㅎㅎ🌕🙏

오늘은 photoLibraryDidChange 대해서 자세히 알아보려고 합니다.

 

iOS 14+ ) Select Photos 권한 작업 (1)

iOS 14+ ) Select Photos 권한 작업 (2)

글에서 photoLibraryDidChange를 한번 봤었는데요,

파라미터로 있는 changeInstance의 사용법(?)이 궁금해서 공부를 해보려고 합니다.

photoLibraryDidChange는 observer에게 사진 라이브러리에 뭔가 변경사항이 발생했음을 알리는 메소드에요.

저 changeInstance는 이름에서도 유추가 가능하실텐데, 뭐가 변했는지..변경사항을 나타내는 객체에요.

그래서 이 changeInstance를 사용하여 변경사항을 확인하고, 자세한 변경정보를 가져올 수 있습니다.

 

그럼 코드로 한번 봅시다. 

github.com/Zedd0202/iOS14_Photos_limited_authorization

 

Zedd0202/iOS14_Photos_limited_authorization

Contribute to Zedd0202/iOS14_Photos_limited_authorization development by creating an account on GitHub.

github.com

요 프로젝트를 보면서 공부해볼게요!

// MARK: - PHPhotoLibraryChangeObserver
func photoLibraryDidChange(_ changeInstance: PHChange) {
    self.getCanAccessImages()
}

저번에는 photoLibraryDidChange에 이렇게만 되어있었어요. 

대충 설명드리자면, 사진 더 선택할래? ➞ ㅇㅇ ➞ 사진 더 선택 photoLibraryDidChange 불림 ➞ getCanAccessImages에서 전체 asset을 가져온 뒤, 접근 할 수 있는 사진 거름 ➞ collectionView에 보여줌.

⚠️⚠️⚠️⚠️

photoLibraryDidChange가 사진 더 선택할래? 에서만 불리는게 아니라

처음 사진 권한 설정 ➞ limited권한 선택 ➞ PHPicker에서 사진 선택 ➞ Done누른 후에도 호출 됨

⚠️⚠️⚠️⚠️

 

여기서 문제는, getCanAccessImages의 

func getCanAccessImages() {
    self.canAccessImages = []
    let requestOptions = PHImageRequestOptions()
    requestOptions.isSynchronous = true

    let fetchOptions = PHFetchOptions()
    self.fetchResult = PHAsset.fetchAssets(with: fetchOptions) // 여기
    ...
 }

이 부분입니다. 만약 사진을 3개를 더 선택했어요. 근데 전체 asset을 다시 가져와야하는 상황인거죠. 

(자세한 코드는 iOS 14+ ) Select Photos 권한 작업 (2). 참고)

 

저는 그게 싫은겁니다!!!

 photoLibraryDidChange의 changeInstance를 사용해 딱 내가 추가한(또는 제거한) asset만 가져올 수 있지않을까! 라는 생각입니다. 

이해가셨나요 ㅠㅠ?....이 글이 뭔가 철저히 제 경험/생각 위주라..저만 알아볼 수 있는 글이 될까 걱정이네요. 

 

 

# 변경사항 가져오기

func getCanAccessImages() {
    self.canAccessImages = []
    let requestOptions = PHImageRequestOptions()
    requestOptions.isSynchronous = true

    let fetchOptions = PHFetchOptions()
    self.fetchResult = PHAsset.fetchAssets(with: fetchOptions) // 여기
    ...
 }

 자..fetchResult라는 변수 안에 fetchAsset을 한 결과를 할당해주고있습니다. 

변경사항은 changeDetails(for:)이라는 메소드로 가져올 수 있는데요, 

var fetchResult = PHFetchResult<PHAsset>()

// MARK: - PHPhotoLibraryChangeObserver
func photoLibraryDidChange(_ changeInstance: PHChange) {
    let details = changeInstance.changeDetails(for: self.fetchResult)
    ..
}

이렇게 기존의 fetchResult를 넣어서 변경사항을 가져올 수 있습니다.

그래서 fetchResult가 할당되지 않은 상태에서 changeDetails를 호출하면 nil이 나오니 이 점 유의하세요!

상황을 하나 가정해보겠습니다.

1. 제가 기존에 limited권한을 주고, 사진을 3개 선택했었어요. 

2. 사진 더 선택할래? alert이 떴을 때 ㅇㅋ하고 사진을 3개를 더 선택합니다.

3. photoLibraryDidChange가 불립니다. 

이렇게 해서 저 details를 출력해보면,

var fetchResult = PHFetchResult<PHAsset>()

// MARK: - PHPhotoLibraryChangeObserver
func photoLibraryDidChange(_ changeInstance: PHChange) {
    let details = changeInstance.changeDetails(for: self.fetchResult)
    ..
}

<PHFetchResultChangeDetails: 0x282ad2d60> before=<PHFetchResult: 0x2839e4210> count=3, after=<PHFetchResult: 0x2839d1a20> count=6, hasIncremental=0 deleted=(null), inserted=(null), changed=(null), hasMoves=0)

대충 이런 결과를 얻을 수 있습니다. 

before, after가 있어서 변경사항을 한눈에 볼 수 있죠!

before에서는 선택된 asset이 3개였는데, after에는 6개가 된 걸 보니 3개를 더 선택했구나~ 라는 걸 알 수 있어요

// MARK: - PHPhotoLibraryChangeObserver
func photoLibraryDidChange(_ changeInstance: PHChange) {
   guard let details = changeInstance.changeDetails(for: self.fetchResult) else { return }
   details.fetchResultBeforeChanges // <PHFetchResult: 0x2806c0370> count=3
   details.fetchResultAfterChanges // <PHFetchResult: 0x2806d8c60> count=6
}

이렇게 fetchResultBeforeChanges, fetchResultAfterChanges를 통해

before, after를 각각 가져올 수도 있습니다.

 

보면 count만 가져올 수 있는 것 같은데..그건 아닙니다! 

guard let details = changeInstance.changeDetails(for: self.fetchResult) else { return }

details.fetchResultAfterChanges.enumerateObjects { (asset, _, _) in 
  //
}

이렇게 enumerateObjects를 통해 asset을 가져올 수도 있습니다.

주의하셔야 할 건, 내가 방금 3개를 "더 선택"했잖아요?

afeterChange에 그 추가된 3개만 들어있는게 아니라, 내가 이전에 선택한 3개도 "같이" 들어있는겁니다.

만약 

코드가 눈에 들어오실진 모르겠는데;;;;

photoLibraryDidChange에서 getCanAccessImages()메소드를 호출하는게 아니라,

fetchResultAfterChanges에서 나온 asset들만 추가하는거죠. 

 

저는 canAccessImages가 CollectionView의 DataSource인데, 이 친구를 비우지 않고, 그냥 바로 append해버리면 

이전 3개 + 최종 선택된 6개 => 9개가 되어 사진이 중복되어 나옵니다.

그러니까 

// MARK: - PHPhotoLibraryChangeObserver
func photoLibraryDidChange(_ changeInstance: PHChange) {
    self.canAccessImages = []
    ....
}

뭐 대충 이런식으로 해주면 되겠죠??

 

 

# 사진 앨범 변경사항 체크 

limited권한의 사진 선택/선택 해제와는 상관없이 photoLibraryDidChange에서는 사진 앨범의 변경사항도 알 수 있습니다. 

details.changedIndexes, details.changedObjects
details.removedIndexes, details.removedObjects
details.insertedIndexes, details.insertedObjects

말 그대로 사진 앨범에서 뭔가 변하고, 지워지고, 추가되면 이 프로퍼티의 변화를 볼 수 있습니다.

limited권한시의 선택/선택 해제와는 상관없습니다. (하면 전부 nil, [] 나옴)

그럼 테스트해봅시다!

 

# 사진 추가 / 제거 

앱을 실행시켜주고, 홈으로 가서 Photos앱을 실행시켜줍니다. 그리고 사진을 하나 추가해줬습니다.

그리고 다시 제 앱으로 돌아오겠습니다.

<PHFetchResultChangeDetails: 0x600002c51320> before=<PHFetchResult: 0x600003f70630> count=7, after=<PHFetchResult: 0x600003f64b00> count=8, hasIncremental=1 deleted=(null), inserted=<NSMutableIndexSet: 0x600000697540>[number of indexes: 1 (in 1 ranges), indexes: (7)], changed=(null), hasMoves=0

그럼 이렇게 detail이 찍히게 됩니다. count가 7에서 8로 늘어났고, inserted에 변화가 생겼네요!

사진이 추가가 됐으니까요..!? 

insertedIndexes로 지금 추가된 asset들의 인덱스를 구할 수 있고, insertedObjects로 추가된 asset들을 가져올 수 있어요.

insertedObjects는 배열입니다! 현재 1개만 추가되었으니 배열에 1개만 들어가있어요.

제거는 굳이 안해볼건데, removedIndexes, removedObjects로 정보를 가져 올 수 있습니다.

 

# 사진 메타데이터 업데이트

changedIndexes, changedObjects는 메타데이터의 변화가 생긴 친구들을 가져올 수 있습니다.

예를들어,

썸네일이 왜 안나오는지는 모르겠음;;;;;;

Edit을 눌러 이미지를 편집한 뒤 다시 제 앱으로 들어오면, photoLibraryDidChange가 불리고 

changedIndexes는 메타데이터가 바뀐 asset들의 인덱스, changedObjects는 메타데이터가 바뀐 asset들을 가져올 수 있습니다.

<PHAsset: 0x7fbaef2175f0> 4DFA2DC9-3EAC-46B2-88EB-2D22497065CE/L0/001 mediaType=1/0, sourceType=1, (300x299), creationDate=2020-09-17 05:57:50 +0000, location=0, hidden=0, favorite=0, adjusted=1

adjusted가 바뀌었죠? hidden, favorite같이 사진을 숨기거나, favorite에 추가해도 메타데이터가 변한거기때문에..불리게됩니다!

 

# hasIncrementalChanges

// MARK: - PHPhotoLibraryChangeObserver
func photoLibraryDidChange(_ changeInstance: PHChange) {
    guard let details = changeInstance.changeDetails(for: self.fetchResult) else { return }
    print(details.hasIncrementalChanges)
}

hasIncrementalChanges라는 프로퍼티도 있는데요, fetch결과의 변경사항을 "증분적으로" 나타낼 수 있는지 여부를 나타내는 Bool값이에요.

Q : 증분적으로..?? 이게 먼 소리.....ㅋㅋ

A :

hasIncrementalChanges가 false이면, fetch결과가 "원래 상태와 너무 다르기 때문에" incremental change information(증분 변경 정보)가 의미가 없습니다. 그러니까 기존 상태와 너무 달라지면 이 값이 false가 된다는 말 같아요. 이럴 때는 그냥  fetchResultAfterChanges를 사용하면 됩니다. 

hasIncrementalChanges가 true면 위에서 언급한 insertedIndexesremovedIndexes,  changedIndexes (or insertedObjectsremovedObjectschangedObjects) 프로퍼티들을 사용하여 추가, 제거, 업데이트 된 객체들을 찾을 수 있는거구요. 

 

이렇게 photoLibraryDidChange를 보는 건 마치겠습니다.

photoLibraryDidChange를 공부하면서 

iOS 14+ ) Select Photos 권한 작업 (1)

iOS 14+ ) Select Photos 권한 작업 (2)

에 부족한 설명들이 몇개 있어서 ㅠㅠㅠ 급하게 추가했네요.

혹시 글에 틀린 설명이 있다면 댓글 부탁드립니다!

즐거운 추석 되세요~.~

반응형