https://moonggi-dev-story.tistory.com/46 해당 게시글에 이어서
이번엔 디바이스에 저장되어있는 이미지를 드래그 앤 드롭으로 CollectionView의 순서를 바꾸는 동작을 진행하자.
해당 기능을 사용하기 위해는 DataSoruce 를 사용해야 한다.
하지만 기존에 RxCocoa 상에서 그대로 DataSoruce를 사용하게 되면 오류를 발생한다.
그러기 때문에 DataSoruce를 따로 만들어서 사용한다.
1. DataSoruce
import UIKit
import RxCocoa
import RxSwift
class DragDataSoruce : NSObject, UICollectionViewDataSource, RxCollectionViewDataSourceType {
typealias Element = [UIImage]
var values = [UIImage]()
func collectionView(_ collectionView: UICollectionView, observedEvent: Event<[UIImage]>) {
if case .next(let element) = observedEvent {
values = element
collectionView.reloadData()
}
}
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let item = values.remove(at: sourceIndexPath.row)
values.insert(item, at: destinationIndexPath.row)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return values.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "GalleryItem", for: indexPath) as! GalleryItem
cell.imageview.image = values[indexPath.row]
return cell
}
}
2. ViewController
import UIKit
import RxCocoa
import RxSwift
import Photos
class DragViewContoller: UIViewController {
let disposeBag = DisposeBag()
let dragDataSoruce = DragDataSoruce()
let listObservable = PublishSubject<[UIImage]>()
@IBOutlet weak var mCollView: UICollectionView!
var imageArray = Array<UIImage>()
override func viewDidLoad() {
super.viewDidLoad()
mCollView.register(UINib(nibName: "GalleryItem", bundle: nil), forCellWithReuseIdentifier: "GalleryItem")
mCollView.allowsSelection = false
mCollView.allowsMultipleSelection = false
mCollView.rx.setDelegate(self).disposed(by: disposeBag)
guard let layout = mCollView.collectionViewLayout as? UICollectionViewFlowLayout else { return }
layout.itemSize = CGSize(width: ((self.view.bounds.width - 20) / 3), height: ((self.view.bounds.width - 20) / 3))
let gesture = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPressGesture(_:)))
mCollView.addGestureRecognizer(gesture)
initCollView()
allGrabPhotos()
}
func initCollView() {
listObservable.asObserver()
.bind(to: mCollView.rx.items(dataSource: dragDataSoruce))
.disposed(by: disposeBag)
}
func allGrabPhotos(){
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
if let img = image
{
self.imageArray.append(img)
}
})
}
}
self.listObservable.onNext(self.imageArray)
}
}
@objc func handleLongPressGesture(_ gesture: UILongPressGestureRecognizer) {
guard let collectionView = mCollView else {
return
}
switch gesture.state {
case .began:
guard let targetIndexPath = collectionView.indexPathForItem(at: gesture.location(in: collectionView)) else {
return
}
collectionView.beginInteractiveMovementForItem(at: targetIndexPath)
case .changed:
collectionView.updateInteractiveMovementTargetPosition(gesture.location(in: collectionView))
case .ended:
collectionView.endInteractiveMovement()
default:
collectionView.cancelInteractiveMovement()
}
}
}
뷰 컨트롤 상에서 롱클릭 제스처 줘서 해당 이벤트는 CollectionView DataSoruce에서 처리한다
해당 2가지 파일만 작성하면 간단하게 drag and drop 을 구현할 수가 있다.
3. 실행결과
CollectionView 구현할때 셀 사이즈 고정을 위해 eslmate size를 none으로 설정해야 합니다.
대부분 갤러리 관련 앱을 만들 때 첫 번째 아이템 같은 경우는 default 이미지를 둬서 전화면이나 이미지를 다시 선택할 수 있게 프로세스를 두곤 합니다. 물론 첫 번째 아이템에서는 드래그 앤 드롭이 안 먹혀야 하고 이동도 불가능하게 만들어야겠죠?
extension DragViewContoller: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, targetIndexPathForMoveFromItemAt originalIndexPath: IndexPath, toProposedIndexPath proposedIndexPath: IndexPath) -> IndexPath {
if proposedIndexPath.row == 0 {
return IndexPath(row: 1, section: proposedIndexPath.section)
} else {
return proposedIndexPath
}
}
}
첫 번째 아이템이 아닌 두 번째 아이템 이후 아이템을 꾹 눌렀을 때 첫 번째 아이템 쪽으로 침범해도 움직여지지 않게 이벤트를 주는 로직입니다.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: (indexPath.row == 0) ? "첫번째아이템" : "나머지아이템", for: indexPath)
if indexPath.row == 0 {
let castingCell = cell as! "첫번째아이템"
castingCell.mContentView.rx.tapGesture()
.when(.recognized)
.subscribe(onNext : {
[self] _ in
//첫번째 아이템 클릭 이벤트
}).disposed(by: castingCell.cellDisposedBag)
return castingCell
} else {
let castingCell = cell as! "나머지아이템"
castingCell.mImageView.image = values[indexPath.row].image
return castingCell
}
}
func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
if indexPath.row == 0 {
return false
} else {
return true
}
}
편의상 첫번째 아이템과 나머지 아이템을 분리시켰습니다. 분리 후 클릭 이벤트 및 이미지 설정 가능합니다. canMoveItemAt 함수에서는 드래그 활성화 여부 체크합니다.
참고 :
https://github.com/ReactiveX/RxSwift/issues/1081
https://www.youtube.com/watch?v=VrW_6EixIVQ
'IOS' 카테고리의 다른 글
Swift - ReactorKit2 (0) | 2021.07.06 |
---|---|
Swift - ReactorKit (0) | 2021.06.09 |
Swift - Photos + CollectionView + Gesture Multiple Select (0) | 2021.03.07 |
프로젝트에 SwiftLint를 달아보자 (0) | 2021.03.01 |
SwiftUI - ViewPager 관련 기능을 달아보자 (0) | 2021.02.16 |