본문 바로가기

IOS

Swift - Photos + CollectionView + Gesture Multiple Select

디바이스에 저장되어있는 이미지를 불러와서 CollectionView 에다가 적용하고

CollectionView에 제스쳐 이벤트를 통해 다중선택 및 단일 선택 할수 있는 기능을 적용한다.

CollectionView를 만드는 편의성을 높히기 위해 RxSwift, RxCocoa를 사용 하였다

 

1. RxSwift, RxCocoa 라이브러리 설치 (pod)

pod 'RxSwift'
pod 'RxCocoa'

2. 저장공간 권한 획득 (Info.plist)

<key>NSPhotoLibraryUsageDescription</key>
<string>This app requires access to the photo library.</string>

3. 디바이스 이미지 가져오기 

func grabPhotos(){
  imageArray = []

  DispatchQueue.global(qos: .background).async {

  let imgManager=PHImageManager.default()

  let requestOptions=PHImageRequestOptions()
  requestOptions.isSynchronous=true
  requestOptions.deliveryMode = .highQualityFormat

  let fetchOptions=PHFetchOptions()
  fetchOptions.sortDescriptors=[NSSortDescriptor(key:"creationDate", ascending: false)]

  let fetchResult: PHFetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions)

  if fetchResult.count > 0 {
    for i in 0..<fetchResult.count{
      imgManager.requestImage(for: fetchResult.object(at: i) as PHAsset, targetSize: CGSize(width:50, height: 50),contentMode: .aspectFill, options: requestOptions, resultHandler: { (image, error) in
      self.imageArray.append(image!)
      })
    }
  }

  self.listObservable.onNext(self.imageArray)
  }
 }

 


4. 콜렉션뷰 셋팅

class ViewController: UIViewController {
  @IBOutlet var mCollView: UICollectionView!
  let listObservable = PublishSubject<[UIImage]>()
  var imageArray = Array<UIImage>()
  let disposeBag = DisposeBag()

  private let sectionInsets = UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0)

  override func viewDidLoad() {
    super.viewDidLoad()

    mCollView.delegate = self
    mCollView.register(UINib(nibName: "GalleryItem", bundle: nil), forCellWithReuseIdentifier: "GalleryItem")

    mCollView.allowsSelection = true
    mCollView.allowsMultipleSelection = false

    if let flowLayout = mCollView.collectionViewLayout as? UICollectionViewFlowLayout {
    flowLayout.minimumLineSpacing = sectionInsets.left
    flowLayout.minimumInteritemSpacing = sectionInsets.left
    flowLayout.sectionInset = sectionInsets
  }

  initCollView()

  setEditing(true, animated: true)
}
func initCollView()
{
  listObservable.subscribe(on: MainScheduler.instance)
  .asObservable()
  .bind(to: mCollView.rx.items(cellIdentifier: "GalleryItem", cellType: GalleryItem.self))
  {
  index, item, cell in
  cell.imageview.image = item

  }.disposed(by: disposeBag)
}
extension ViewController: UICollectionViewDelegate {

    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        
        if isEditing == false {
            collectionView.deselectItem(at: indexPath, animated: false)
        }
    }
    
    func collectionView(_ collectionView: UICollectionView, shouldBeginMultipleSelectionInteractionAt indexPath: IndexPath) -> Bool {
        return true
    }
    
    func collectionView(_ collectionView: UICollectionView, didBeginMultipleSelectionInteractionAt indexPath: IndexPath) {
        setEditing(true, animated: true)
    }
    
    func collectionViewDidEndMultipleSelectionInteraction(_ collectionView: UICollectionView) {
        print("\(#function)")
    }
    
}
class GalleryItem: UICollectionViewCell {
    
    var cellDisposedBag = DisposeBag()
    
    @IBOutlet weak var backview: UIView!
    @IBOutlet weak var imageview: UIImageView!
    
    override func prepareForReuse() {
        super.prepareForReuse()
        cellDisposedBag = DisposeBag()
        isSelected = false
        showSelectionOverlay()
    }
    
    private func showSelectionOverlay() {
        
        if isSelected
        {
            backview.backgroundColor = UIColor.red
            backview.alpha = 0.5
            
        }
        else
        {
            backview.backgroundColor = UIColor.clear
            backview.alpha = 1.0
        }
    }
    
    func configureCell() {
        isSelected = true
        showSelectionOverlay()
    }
    
    
    override var isSelected: Bool {
        didSet {
            showSelectionOverlay()
            setNeedsLayout()
        }
    }
}
override func setEditing(_ editing: Bool, animated: Bool) {
        guard isEditing != editing else {
            return
        }

        super.setEditing(editing, animated: animated)
        mCollView.allowsMultipleSelection = editing

        clearSelectedItems(animated: true)
    }

    func clearSelectedItems(animated: Bool) {
        mCollView.indexPathsForSelectedItems?.forEach({ (indexPath) in
            mCollView.deselectItem(at: indexPath, animated: animated)
        })
        mCollView.reloadItems(at: mCollView.indexPathsForVisibleItems)
    }

5. 실행 결과


6. 전체 소스

import UIKit
import RxCocoa
import RxSwift
import Photos

class ViewController: UIViewController {
    
    @IBOutlet var mCollView: UICollectionView!
    let listObservable = PublishSubject<[UIImage]>()
    let disposeBag = DisposeBag()
    var imageArray = Array<UIImage>()
    
    private let sectionInsets = UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        mCollView.delegate = self
        mCollView.register(UINib(nibName: "GalleryItem", bundle: nil), forCellWithReuseIdentifier: "GalleryItem")
        
        mCollView.allowsSelection = true
        mCollView.allowsMultipleSelection = false

        if let flowLayout = mCollView.collectionViewLayout as? UICollectionViewFlowLayout {
            flowLayout.minimumLineSpacing = sectionInsets.left
            flowLayout.minimumInteritemSpacing = sectionInsets.left
            flowLayout.sectionInset = sectionInsets
        }

        initCollView()

        setEditing(true, animated: true)
        
        grabPhotos()
    }
    
    func grabPhotos(){
        imageArray = []

        DispatchQueue.global(qos: .background).async {
            
            let imgManager=PHImageManager.default()

            let requestOptions=PHImageRequestOptions()
            requestOptions.isSynchronous=true
            requestOptions.deliveryMode = .highQualityFormat

            let fetchOptions=PHFetchOptions()
            fetchOptions.sortDescriptors=[NSSortDescriptor(key:"creationDate", ascending: false)]

            let fetchResult: PHFetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions)
            
            if fetchResult.count > 0 {
                for i in 0..<fetchResult.count{
                    imgManager.requestImage(for: fetchResult.object(at: i) as PHAsset, targetSize: CGSize(width:50, height: 50),contentMode: .aspectFill, options: requestOptions, resultHandler: { (image, error) in
                        self.imageArray.append(image!)
                    })
                }
            }
            
            self.listObservable.onNext(self.imageArray)
        }
    }

    func initCollView()
    {
        listObservable.subscribe(on: MainScheduler.instance)
            .asObservable()
            .bind(to: mCollView.rx.items(cellIdentifier: "GalleryItem", cellType: GalleryItem.self))
            {
                index, item, cell in
                cell.imageview.image = item

            }.disposed(by: disposeBag)

    }

    override func setEditing(_ editing: Bool, animated: Bool) {
        guard isEditing != editing else {
            return
        }

        super.setEditing(editing, animated: animated)
        mCollView.allowsMultipleSelection = editing

        clearSelectedItems(animated: true)
    }

    func clearSelectedItems(animated: Bool) {
        mCollView.indexPathsForSelectedItems?.forEach({ (indexPath) in
            mCollView.deselectItem(at: indexPath, animated: animated)
        })
        mCollView.reloadItems(at: mCollView.indexPathsForVisibleItems)
    }
}

extension ViewController: UICollectionViewDelegate {

    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        
        if isEditing == false {
            collectionView.deselectItem(at: indexPath, animated: false)
        }
    }
    
    func collectionView(_ collectionView: UICollectionView, shouldBeginMultipleSelectionInteractionAt indexPath: IndexPath) -> Bool {
        return true
    }
    
    func collectionView(_ collectionView: UICollectionView, didBeginMultipleSelectionInteractionAt indexPath: IndexPath) {
        setEditing(true, animated: true)
    }
    
    func collectionViewDidEndMultipleSelectionInteraction(_ collectionView: UICollectionView) {
        print("\(#function)")
    }
    
}

class GalleryItem: UICollectionViewCell {
    
    var cellDisposedBag = DisposeBag()
    
    @IBOutlet weak var backview: UIView!
    @IBOutlet weak var imageview: UIImageView!
    
    override func prepareForReuse() {
        super.prepareForReuse()
        cellDisposedBag = DisposeBag()
        isSelected = false
        showSelectionOverlay()
    }
    
    private func showSelectionOverlay() {
        
        if isSelected
        {
            backview.backgroundColor = UIColor.red
            backview.alpha = 0.5
            
        }
        else
        {
            backview.backgroundColor = UIColor.clear
            backview.alpha = 1.0
        }
    }
    
    func configureCell() {
        isSelected = true
        showSelectionOverlay()
    }
    
    
    override var isSelected: Bool {
        didSet {
            showSelectionOverlay()
            setNeedsLayout()
        }
    }
}

 

 

아래 애플 공식문서에 좀더 자세한 예시 프로젝트가 있으니까 꼭 확인 하세요!

 

참고 :

https://developer.apple.com/documentation/uikit/uitableviewdelegate/selecting_multiple_items_with_a_two-finger_pan_gesture

 

Apple Developer Documentation

 

developer.apple.com

https://github.com/Akhilendra/photosAppiOS

 

Akhilendra/photosAppiOS

This is the source code for my photo gallery app tutorial on Youtube https://youtu.be/QS2mWk3fAWc - Akhilendra/photosAppiOS

github.com