Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- collectionview
- UICollectionView
- rxswift
- ios
- Clean Code
- swiftUI
- Xcode
- Human interface guide
- combine
- uitableview
- Observable
- Refactoring
- 애니메이션
- swift documentation
- uiscrollview
- Protocol
- clean architecture
- 스위프트
- map
- UITextView
- 리팩토링
- MVVM
- 클린 코드
- ribs
- 리펙터링
- HIG
- 리펙토링
- tableView
- SWIFT
- RxCocoa
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swift] DragDropCollectionView 프레임워크 본문
준비
- 코드로 UI를 편리하게 작성하기 위해서 Then, SnapKit 프레임워크 설치
pod 'Then' pod 'SnapKit' pod 'Reusable'
사용 방법
- 의존성 도구 없이 DragDropCollectionView 파일을 복사 붙여넣기하여 사용
(아래 github에 명시)
- DragDropCollectionView.swift을 리펙토링하여 아래처럼 사용
// // DragDropCollectionView.swift // DragDrop // // Created by Lior Neu-ner on 2014/12/30. // Copyright (c) 2014 LiorN. All rights reserved. // 3rd test for git submodule //Just testing git subtree for the second time import UIKit import AVFoundation protocol DrapDropCollectionViewDelegate: AnyObject { func dragDropCollectionViewDidMoveCellFromInitialIndexPath<T>( _ collectionView: DragDropCollectionView<T>, initialIndexPath: IndexPath, toNewIndexPath newIndexPath: IndexPath ) func dragDropCollectionViewDraggingDidBeginWithCellAtIndexPath<T>(_ collectionView: DragDropCollectionView<T>, indexPath: IndexPath) func dragDropCollectionViewDraggingDidEndForCellAtIndexPath<T>(_ collectionView: DragDropCollectionView<T>, indexPath: IndexPath) } class DragDropCollectionView<DraggableCellType>: UICollectionView, UIGestureRecognizerDelegate where DraggableCellType: UICollectionViewCell { weak var draggingDelegate: DrapDropCollectionViewDelegate? var longPressRecognizer = UILongPressGestureRecognizer().then { $0.delaysTouchesBegan = false $0.cancelsTouchesInView = false $0.numberOfTouchesRequired = 1 $0.minimumPressDuration = 0.5 $0.allowableMovement = 10.0 } var draggedCellIndexPath: IndexPath? var draggingView: UIView? var touchOffsetFromCenterOfCell: CGPoint? let pingInterval = 0.03 var isSwapEnabled = true required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) self.commonInit() } override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) { super.init(frame: frame, collectionViewLayout: layout) self.commonInit() } func commonInit() { self.longPressRecognizer.addTarget(self, action: #selector(self.handleLongPress(_:))) self.longPressRecognizer.isEnabled = false self.addGestureRecognizer(self.longPressRecognizer) } @objc func handleLongPress(_ longPressRecognizer: UILongPressGestureRecognizer) { let touchLocation = longPressRecognizer.location(in: self) switch longPressRecognizer.state { case .began: self.draggedCellIndexPath = self.indexPathForItem(at: touchLocation) guard let draggedCellIndexPath = self.draggedCellIndexPath, self.cellForItem(at: draggedCellIndexPath) is DraggableCellType, case _ = self.draggingDelegate?.dragDropCollectionViewDraggingDidBeginWithCellAtIndexPath(self, indexPath: draggedCellIndexPath), let draggedCell = self.cellForItem(at: draggedCellIndexPath) else { return } let draggingView = UIImageView(image: self.getRasterizedImageCopyOfCell(draggedCell)) self.draggingView = draggingView draggingView.center = (draggedCell.center) self.addSubview(draggingView) draggedCell.isHidden = true self.touchOffsetFromCenterOfCell = CGPoint(x: draggedCell.center.x - touchLocation.x, y: draggedCell.center.y - touchLocation.y) UIView.animate( withDuration: 0.4, animations: { draggingView.transform = .init(scale: 1.1) draggingView.alpha = 0.9 draggingView.layer.shadowRadius = 20 draggingView.layer.shadowColor = UIColor.Slide.nero.cgColor draggingView.layer.shadowOpacity = 0.2 draggingView.layer.shadowOffset = CGSize(width: 0, height: 25) } ) case .changed: guard self.draggedCellIndexPath != nil, let touchOffsetFromCenterOfCell = self.touchOffsetFromCenterOfCell else { return } self.draggingView?.center = CGPoint( x: touchLocation.x + touchOffsetFromCenterOfCell.x, y: touchLocation.y + touchOffsetFromCenterOfCell.y ) self.dispatchOnMainQueueAfter( self.pingInterval, closure: { let shouldSwapCellsTuple = self.shouldSwapCells(touchLocation) if shouldSwapCellsTuple.shouldSwap { guard let newIndexPath = shouldSwapCellsTuple.newIndexPath else { return } self.swapDraggedCellWithCellAtIndexPath(newIndexPath) } } ) case .ended: guard let draggedCellIndexPath = self.draggedCellIndexPath, case _ = self.draggingDelegate?.dragDropCollectionViewDraggingDidEndForCellAtIndexPath(self, indexPath: draggedCellIndexPath), let draggedCell = self.cellForItem(at: draggedCellIndexPath) else { return } UIView.animate( withDuration: 0.4, animations: { self.draggingView?.transform = .identity self.draggingView?.alpha = 1.0 self.draggingView?.center = draggedCell.center self.draggingView?.layer.shadowRadius = 0 self.draggingView?.layer.shadowColor = nil self.draggingView?.layer.shadowOffset = .zero }, completion: { finished -> Void in self.draggingView?.removeFromSuperview() self.draggingView = nil draggedCell.isHidden = false self.draggedCellIndexPath = nil } ) default: break } } func enableDragging(_ enable: Bool) { self.longPressRecognizer.isEnabled = enable } fileprivate func shouldSwapCells(_ previousTouchLocation: CGPoint) -> (shouldSwap: Bool, newIndexPath: IndexPath?) { guard self.isSwapEnabled, case let currentTouchLocation = self.longPressRecognizer.location(in: self), let draggedCellIndexPath = self.draggedCellIndexPath, Double(currentTouchLocation.x).isNaN.toggled, Double(currentTouchLocation.y).isNaN.toggled, self.distanceBetweenPoints(previousTouchLocation, secondPoint: currentTouchLocation) < CGFloat(20.0), let newIndexPathForCell = self.indexPathForItem(at: currentTouchLocation), self.cellForItem(at: draggedCellIndexPath) is DraggableCellType, self.cellForItem(at: newIndexPathForCell) is DraggableCellType, newIndexPathForCell != draggedCellIndexPath else { return (false, nil) } return (true, newIndexPathForCell) } fileprivate func swapDraggedCellWithCellAtIndexPath(_ newIndexPath: IndexPath) { guard let draggedCellIndexPath = self.draggedCellIndexPath else { return } let generator = UIImpactFeedbackGenerator(style: .light) generator.impactOccurred() self.moveItem(at: draggedCellIndexPath, to: newIndexPath) self.draggingDelegate?.dragDropCollectionViewDidMoveCellFromInitialIndexPath( self, initialIndexPath: draggedCellIndexPath, toNewIndexPath: newIndexPath ) self.draggedCellIndexPath = newIndexPath } } //Assisting Functions extension DragDropCollectionView { func getRasterizedImageCopyOfCell(_ cell: UICollectionViewCell) -> UIImage? { UIGraphicsBeginImageContextWithOptions(cell.bounds.size, false, 0.0) guard let context = UIGraphicsGetCurrentContext() else { return nil } cell.layer.render(in: context) let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return image } func dispatchOnMainQueueAfter(_ delay: Double, closure: @escaping () -> Void) { DispatchQueue.main.asyncAfter( deadline: .now() + delay, qos: .userInteractive, flags: .enforceQoS, execute: closure ) } func distanceBetweenPoints(_ firstPoint: CGPoint, secondPoint: CGPoint) -> CGFloat { let xDistance = firstPoint.x - secondPoint.x let yDistance = firstPoint.y - secondPoint.y return sqrt(xDistance * xDistance + yDistance * yDistance) } }
DragDropCollectionViewDelegate 사용 방법
- CollectionView를 가지고 있는 MyView를 구현하고, ViewController에서 MyView를 사용하는 형식
- ViewController 하는 일
- myView.dragDropCollectionView의 데이터 소스 컨트롤
- myView.dragDropCollectionView의 drag or drop 애니메이션 설정
- MyView안에 DragDropCollectionView UI 코드
// MyView.swift enum Metric { static let collectionViewPadding = 4.0 static let collectionViewNumberOfColumns = 3.0 static let collectionViewInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) static let collectionViewItemSize: CGSize = { let collectionViewLeftRightInset = collectionViewInset.left + collectionViewInset.right let cellsWidth = UIScreen.main.bounds.width - collectionViewLeftRightInset - collectionViewPadding * (collectionViewNumberOfColumns - 1) let width = cellsWidth / collectionViewNumberOfColumns let height = width * 100 / 120 return CGSize(width: width, height: height) }() } let dragDropCollectionView = DragDropCollectionView<MyCell>( frame: .zero, collectionViewLayout: UICollectionViewFlowLayout().then { $0.minimumLineSpacing = Metric.collectionViewPadding $0.minimumInteritemSpacing = Metric.collectionViewPadding $0.itemSize = Metric.collectionViewItemSize } ).then { $0.backgroundColor = .clear $0.enableDragging(true) $0.register(cellType: MyCell.self) }
- ViewController에서 위 collectionView 사용
// ViewController.swift private let containerStackView = UIStackView().then { $0.axis = .vertical } private let addButton = UIButton().then { $0.setTitle("추가", for: .normal) $0.setTitleColor(.systemBlue, for: .normal) $0.setTitleColor(.blue, for: .highlighted) } private let myView = MyView() override func viewDidLoad() { super.viewDidLoad() self.addButton.addTarget(self, action: #selector(addItem), for: .touchUpInside) self.view.addSubview(self.containerStackView) self.containerStackView.addArrangedSubview(self.addButton) self.containerStackView.addArrangedSubview(self.myView) self.containerStackView.snp.makeConstraints { $0.edges.equalTo(self.view.safeAreaLayoutGuide) } }
- dataSource 처리
// ViewController.swift // in viewDidLoad() self.myView.dragDropCollectionView.dataSource = self ... extension ViewController: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { self.colorDataSource.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(for: indexPath, cellType: MyCell.self) cell.prepare(color: self.colorDataSource[indexPath.item]) return cell } }
- drag & drop 델리게이트 준수
- 델리게이트 구현
- didBegin
- didMove
- didEnd
// ViewController.swift // in viewDidLoad() self.myView.dragDropCollectionView.draggingDelegate = self ... extension ViewController: DrapDropCollectionViewDelegate { func dragDropCollectionViewDraggingDidBeginWithCellAtIndexPath<T>(_ collectionView: DragDropCollectionView<T>, indexPath: IndexPath) where T : UICollectionViewCell { let cell = collectionView.dequeueReusableCell(for: indexPath, cellType: MyCell.self) cell.alpha = 0.1 } func dragDropCollectionViewDidMoveCellFromInitialIndexPath<T>(_ collectionView: DragDropCollectionView<T>, initialIndexPath: IndexPath, toNewIndexPath newIndexPath: IndexPath) where T : UICollectionViewCell { let item = self.colorDataSource[initialIndexPath.item] self.moveItem(from: initialIndexPath, to: newIndexPath, item: item) } func dragDropCollectionViewDraggingDidEndForCellAtIndexPath<T>(_ collectionView: DragDropCollectionView<T>, indexPath: IndexPath) where T : UICollectionViewCell { let cell = collectionView.dequeueReusableCell(for: indexPath, cellType: MyCell.self) cell.alpha = 1 } }
- 델리게이트 구현
* 전체 코드: https://github.com/JK0369/ExDragDropCollectionView
* 참고
'iOS framework' 카테고리의 다른 글
[iOS - swift] UITextView placeholder 프레임워크 (UITextView+Placeholder) (0) | 2022.01.30 |
---|---|
[iOS - swift] SkeletonView 스켈레톤 뷰 (로딩 뷰) (3) | 2022.01.19 |
[iOS - swift] Starscream을 이용한 WebSockets (웹 소켓) 사용 방법 (0) | 2022.01.08 |
[iOS - swift] Firebase Analytics 사용 방법, 이벤트 로깅, logEvent() (5) | 2022.01.07 |
[iOS - swift] Cauli 프레임워크 (앱에서 네트워킹 로깅 확인 툴) (2) | 2021.12.30 |
Comments