i try to show image gallery after user permission to allow all photos, but the gallery is not showing. but when i back to previous controller and navigate back, the gallery show up. but that's not what i want, i want after user allow the image show up.
this my setup
private var allPhotos: [PHAsset] = []
override func viewDidLoad() {
super.viewDidLoad()
PHPhotoLibrary.shared().register(self)
setupCollectionView()
checkPhotoLibraryPermission()
bindViewModel()
}
deinit {
PHPhotoLibrary.shared().unregisterChangeObserver(self)
}
private func bindViewModel() {
let dataSource = Observable.just(allPhotos)
dataSource.asObservable()
.bind(to: collectionView.rx.items(cellIdentifier: GalleryCollectionViewCell.cellId, cellType: GalleryCollectionViewCell.self)) { row, asset, cell in
let imageRequestOptions = PHImageRequestOptions()
imageRequestOptions.resizeMode = .exact
self.imageManager.requestImageDataAndOrientation(for: asset, options: imageRequestOptions) { imageData, _, orientation, info in
guard let imageData = imageData else { return }
cell.setup(imageData: imageData)
}
}.disposed(by: disposedBag)
collectionView.rx.itemSelected
.subscribe(onNext: { [weak self] indexPath in
guard let strongSelf = self else { return }
let asset = strongSelf.allPhotos[indexPath.row]
asset.requestContentEditingInput(with: PHContentEditingInputRequestOptions()) { editingInput, info in
guard let path = editingInput?.fullSizeImageURL?.path.replacingOccurrences(of: "HEIC", with: "PNG") else { return }
self?.imageManager.requestImageDataAndOrientation(for: asset, options: self?.imageRequestOptions) { imageData, _, orientation, info in
guard let imageData = imageData else { return }
self?.goToCropImage(from: imageData, and: path.lastPathComponent)
}
}
}).disposed(by: disposedBag)
}
private func fetchAllPhotos() {
let allPhotosOptions = PHFetchOptions()
allPhotosOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
let fetchResult = PHAsset.fetchAssets(with: .image, options: allPhotosOptions)
allPhotos = fetchResult.objects(at: IndexSet(0..<fetchResult.count))
}
private func checkPhotoLibraryPermission() {
let status = PHPhotoLibrary.authorizationStatus()
switch status {
case .authorized:
fetchAllPhotos()
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
case .denied, .restricted :
//handle denied status
gotoAppSettings()
case .notDetermined:
// ask for permissions
PHPhotoLibrary.requestAuthorization { status in
switch status {
case .authorized:
self.fetchAllPhotos()
case .denied, .restricted:
// as above
self.gotoAppSettings()
case .notDetermined:
// won't happen but still
break
case .limited:
break
@unknown default:
fatalError("Failed to get user permission to access photo")
}
}
case .limited:
fetchAllPhotos()
@unknown default:
fatalError("Failed to get user permission to access photo")
}
}
func photoLibraryDidChange(_ changeInstance: PHChange) {
let allPhotosOptions = PHFetchOptions()
let fetchResult = PHAsset.fetchAssets(with: .image, options: allPhotosOptions)
DispatchQueue.main.async {
self.allPhotos = fetchResult.objects(at: IndexSet(0..<fetchResult.count))
self.collectionView.reloadData()
}
}
I already try to to reload collectionView but it still not show up.
The way that UICollectionView.rx.items
works is that it observes its dataSource. When the dataSource emits a new array, the items operator will reload the collection view and call its closure for each item.
Since you are using just
as your data source, only one array is emitted and the collection view never changes. You have to tie the source to the change observer to get it to work. Here is a working example:
extension PhotosViewController { // a UICollectionViewController
func connect(disposeBag: DisposeBag) {
// initial fetch result
let allPhotosOptions = PHFetchOptions()
allPhotosOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
let initialFetchResult = PHAsset.fetchAssets(with: allPhotosOptions)
let assets = PHPhotoLibrary.shared().rx.registerChangeObserver()
// when a change is observed, we need to update the fetchResult
.scan(initialFetchResult) { oldResult, change in
guard let changes = change.changeDetails(for: oldResult) else { return oldResult }
return changes.fetchResultAfterChanges
}
// but first send the initial asset fetch to the collection view
.startWith(initialFetchResult)
// and get the assets out of the fetch result.
.map { $0.objects(at: IndexSet(0 ..< $0.count)) }
collectionView.dataSource = nil
assets
.observe(on: MainScheduler.instance)
.bind(to: collectionView.rx.items(cellIdentifier: "GridViewCell", cellType: GridViewCell.self)) { _, asset, cell in
cell.configure(asset: asset)
}
.disposed(by: disposeBag)
}
}
extension Reactive where Base: PHPhotoLibrary {
// not actually needed, but I provided it as an example.
static func requestAuthorization() -> Observable<PHAuthorizationStatus> {
Observable.create { observer in
Base.requestAuthorization { status in
observer.onNext(status)
observer.onCompleted()
}
return Disposables.create()
}
}
// this sets up the change observer. Note, your VC isn't the observer.
func registerChangeObserver() -> Observable<PHChange> {
Observable.create { [base] observer in
let changeObserver: RxPhotoLibraryChangeObserver = .init(observer: observer)
base.register(changeObserver)
return Disposables.create { base.unregisterChangeObserver(changeObserver) }
}
}
}
// this is the change observer used in the above.
final class RxPhotoLibraryChangeObserver: NSObject, PHPhotoLibraryChangeObserver {
let observer: AnyObserver<PHChange>
init(observer: AnyObserver<PHChange>) {
self.observer = observer
}
func photoLibraryDidChange(_ changeInstance: PHChange) {
observer.onNext(changeInstance)
}
}