์ต๊ทผ์ ๋ฉ๋ก ๋ฎค์ง ํด๋ก ์ฝ๋ฉ ํ๋ก์ ํธ๋ฅผ ์งํํ์๋๋ฐ์!
ํด๋น ํ๋ก์ ํธ์์๋ ์ปฌ๋ ์
๋ทฐ์์ ์
์ ์ฒด๊ฐ ์๋๋ผ ์
๋ด๋ถ์ ํน์ ๋ฒํผ์ ๋๋ ์ ๋๋ง ๋๋๊ทธ์ค๋๋์ด ๋์ํด์ผ ํ์ต๋๋ค.

UIKit์์ Drag & Drop์ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ํฌ๊ฒ ๋ ๊ฐ์ง๊ฐ ์์ด์.
1. UICollectionViewDragDelegate์ UICollectionViewDropDelegate๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ์
2. LongPress ์ ์ค์ฒ๋ฅผ ํ์ฉํ๋ ๋ฐฉ์
๋ ๊ฐ์ง ๋ฐฉ์์ด ์ด๋ป๊ฒ ๋ค๋ฅธ์ง ๊ฐ๋จํ ์ ๋ฆฌํด๋ณผ๊น์?
1) UICollectionViewDragDelegate · UICollectionViewDropDelegate ์ฌ์ฉ
UIKit์์ ๊ณต์์ ์ผ๋ก ์ ๊ณตํ๋ Drag & Drop ๋ฐฉ์์ ๋๋ค.
iOS 11 ์ดํ๋ก ๋์ ๋ ์์คํ ์ธํฐ๋์ ์ด๋ผ ๊ธฐ๋ณธ ์ ์ค์ฒ์ ์ ๋๋ฉ์ด์ ์ด ์์ฐ์ค๋ฝ๊ฒ ๋์ํ๊ณ ,
๋ฏธ๋ฆฌ ์ ๊ณต๋๋ ๋ค์ํ ๊ธฐ๋ฅ์ ๊ทธ๋๋ก ํ์ฉํ ์ ์๋ค๋ ์ฅ์ ์ด ์์ด์.
์ฅ์
- ์์คํ ์์ ์ ๊ณตํ๋ ๊ธฐ๋ณธ ๋๋๊ทธ ๋์์ด ๋งค์ฐ ์์ฐ์ค๋ฝ์ต๋๋ค.
- ๋ค๋ฅธ ์ ์ค์ฒ์ ์ถฉ๋์ด ๊ฑฐ์ ์์ต๋๋ค.
- drag / drop ์ํ์ ๋ฐ๋ผ delegate๊ฐ ๋ช ํํ๊ฒ ๋ถ๋ฆฌ๋์ด ์์ด ์ ์ง๋ณด์ํ๊ธฐ ํธํฉ๋๋ค.
- ์ธ๋ถ ์ฑ ๊ฐ drag & drop๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์ง์ํฉ๋๋ค.
๋จ์
- ์ ์ ์ฒด๋ฅผ ๊ธธ๊ฒ ๋๋ ์ ๋๋ง ๋๋๊ทธ๊ฐ ์์๋๊ธฐ ๋๋ฌธ์, ๋ฒํผ์ ๋๋ฅผ ๋๋ง ๋๋๊ทธ๋ฅผ ์์ํด์ผ ํ๋ UI์๋ ๋ง์ง ์์ต๋๋ค.
- ๋๋๊ทธ ์ค ์ ์ธํ(์์, ๊ทธ๋ฆผ์ ๋ฑ)์ ์ง์ ์ ์ดํ๊ธฐ ์ด๋ ต์ต๋๋ค.
2) LongPressGestureRecognizer๋ฅผ ์ด์ฉํ interactiveMovement
๋ ๋ฒ์งธ ๋ฐฉ์์ LongPress ์ ์ค์ฒ๋ฅผ ์ปฌ๋ ์
๋ทฐ(๋๋ ํน์ ๋ฒํผ) ์์ ์ง์ ๋ฌ๊ณ , ์ ์ค์ฒ ์ํ์ ๋ฐ๋ผ
beginInteractiveMovement, updateInteractiveMovement, endInteractiveMovement๋ฅผ ํธ์ถํ๋ ๊ตฌ์กฐ์
๋๋ค.
์ฅ์
- ๋ฒํผ์ ๋๋ฌ์๋ง ๋๋๊ทธ๋ฅผ ์์ํ๋ UI์ฒ๋ผ ์ปค์คํ ์๊ตฌ์ฌํญ์ ๊ตฌํํ๊ธฐ ์ข์ต๋๋ค.
- ๋๋๊ทธ ์์ ์ ์ ํ๋, ๊ทธ๋ฆผ์ ์ถ๊ฐ ๋ฑ ๋น์ฃผ์ผ ์ปค์คํฐ๋ง์ด์ง์ด ์์ ๋กญ์ต๋๋ค.
- ์์คํ DragDelegate๋ณด๋ค ๋์ ์ ์ด ๋ฒ์๊ฐ ๋์ต๋๋ค.
๋จ์
- x์ถ ํ๋ค๋ฆผ, indexPath nil ๋ฌธ์ ๋ฑ ์์ธ ์ฒ๋ฆฌ๋ฅผ ์ง์ ํด์ค์ผ ํฉ๋๋ค.
- ์์คํ ๊ธฐ๋ณธ Drag & Drop๋ณด๋ค ์ ์ค์ฒ ์ถฉ๋ ๊ฐ๋ฅ์ฑ์ด ์์ต๋๋ค.
์ ๋ ์
์ ์ฒด๊ฐ ์๋๋ผ ์
์์ ํน์ ๋ฒํผ์ ๋๋ ์ ๋๋ง ๋๋๊ทธ์ค๋๋์ด ์์๋์ด์ผ ํ๊ธฐ ๋๋ฌธ์,
LongPressGestureRecognizer๋ฅผ ์ฌ์ฉํด ๊ตฌํํ์ต๋๋ค!
1. ๋ฒํผ์ LongPress ์ ์ค์ฒ ์ฐ๊ฒฐํ๊ธฐ
๋จผ์ ๋๋๊ทธ๋ฅผ ์์ํ ๋ฒํผ(menuIconView)์ LongPress ์ ์ค์ฒ๋ฅผ ์ถ๊ฐํฉ๋๋ค.
menuIconView.isUserInteractionEnabled = true
menuIconView.addGestureRecognizer(
UILongPressGestureRecognizer(target: self, action: #selector(handleDrag(_:)))
)
UILongPressGestureRecognizer์ ๊ธฐ๋ณธ press duration์ 0.5์ด์
๋๋ค.
๋ฐ์ ์๊ฐ์ ๋ ๋น ๋ฅด๊ฒ ํ๊ณ ์ถ๋ค๋ฉด ์๋์ฒ๋ผ ์กฐ์ ํ ์ ์์ด์.
let longPress = UILongPressGestureRecognizer(target: self,
action: #selector(handleDrag(_:)))
longPress.minimumPressDuration = 0.0 // ์ํ๋ ์๊ฐ์ผ๋ก ๋ณ๊ฒฝ
menuIconView.addGestureRecognizer(longPress)
2. ๋๋๊ทธ ๋์์ ์ฒ๋ฆฌํ๋ handleDrag ๊ตฌํ
@objc private func handleDrag(_ gesture: UILongPressGestureRecognizer) {
guard let cv = superview as? UICollectionView else { return }
guard let indexPath = cv.indexPath(for: self) else { return }
switch gesture.state {
case .began:
cv.beginInteractiveMovementForItem(at: indexPath)
case .changed:
let location = gesture.location(in: cv)
// ์ข์ฐ๋ก ํ๋ค๋ฆฌ์ง ์๋๋ก x ๊ฐ์ ๊ณ ์ ํ์ด์!
let fixedLocation = CGPoint(x: self.center.x, y: location.y)
cv.updateInteractiveMovementTargetPosition(fixedLocation)
case .ended:
cv.endInteractiveMovement()
default:
cv.cancelInteractiveMovement()
}
}
๋๋๊ทธ ์ค์ธ ์
์ ์ฌ์ฉ์์ ์๊ฐ๋ฝ ์์น๋ฅผ ์ค์๊ฐ์ผ๋ก ๋ฐ๋ผ๊ฐ์ผ ํ๊ฒ ์ฃ ?
๋ฐ๋ผ์ gesture๊ฐ ์ ๋ฌํ๋ (x, y) ์ขํ ๊ฐ์ ๊ธฐ๋ฐ์ผ๋ก ์ด๋ ์์น๋ฅผ ๊ณ์ ์ ๋ฐ์ดํธํด์ค์ผ ํด์.
๊ทธ๋ฐ๋ฐ ์ด๋.. ์๋์ ๊ฐ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ๋๋ ์์ต๋๋ค.

interactiveMovement๋ ๊ธฐ๋ณธ์ ์ผ๋ก x์ถ·y์ถ ์ ์ฒด๋ฅผ ๊ทธ๋๋ก ๋ฐ๋ผ๊ฐ๊ธฐ ๋๋ฌธ์,
๋ฆฌ์คํธ๋ทฐ์์๋ ์
์ด ์ข์ฐ๋ก ํ๋ค๋ฆฌ๋ ๋ฌธ์ ๊ฐ ์๊ธธ ์ ์์ด์.
Solution: x์ถ ๊ณ ์ ์ผ๋ก ํ๋ค๋ฆผ ํด๊ฒฐํ๊ธฐ
๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์ ์๋ ์ฝ๋์ฒ๋ผ x ๊ฐ์ self.center.x๋ก ๊ณ ์ ํด ์ํ ์ด๋๋ง ๊ฐ๋ฅํ๋๋ก ๊ตฌํํ๋ฉด ๋ฉ๋๋ค!
let fixedLocation = CGPoint(x: self.center.x, y: location.y)
cv.updateInteractiveMovementTargetPosition(fixedLocation)

๊ทธ๋ผ ์๋ ๊ฒ ํด๊ฒฐ๋ฉ๋๋ค~
3. CollectionView ์ค์
๋ทฐ์ปจํธ๋กค๋ฌ์์๋ dragInteraction์ ๋นํ์ฑํํ๊ณ ๊ธฐ๋ณธ ์ค์ ์ ๋ฃ์ด์คฌ์ต๋๋ค.
private func setupCollectionView() {
cv.dragInteractionEnabled = false
cv.isUserInteractionEnabled = true
cv.isScrollEnabled = true
}
๊ทธ๋ฆฌ๊ณ ์ด๋๋ ์์๋ฅผ ์ค์ ๋ฐ์ดํฐ์ ๋ฐ์ํ๋ ์ฝ๋๊ฐ ๊ผญ ํ์ํด์!
extension MixUpViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView,
moveItemAt sourceIndexPath: IndexPath,
to destinationIndexPath: IndexPath) {
let moved = music.remove(at: sourceIndexPath.item)
music.insert(moved, at: destinationIndexPath.item)
}
}
๋ฐ์ดํฐ ๋ฐฐ์ด์ ์ ๋ฐ์ดํธํ์ง ์์ผ๋ฉด UI๋ ์์ง์ด์ง๋ง ์ค์ ๋ชจ๋ธ์ ์์๋ ๋ณ๊ฒฝ๋์ง ์๊ธฐ ๋๋ฌธ์ ํ์์ ๋๋ค.
์ด๋ ๊ฒ ํ๋ฉด ๊ธฐ๋ณธ์ ์ธ ๋๋๊ทธ์ค๋๋ ๊ธฐ๋ฅ์ ์์ฑ๋ฉ๋๋ค.

ํ์ง๋ง ์ฌ๊ธฐ๊น์ง ๊ตฌํํ๋ฉด ์กฐ๊ธ ๋ฐ๋ฐํ๊ฒ ๋๊ปด์ง ์ ์์ด์.
์ด์ ๋๋๊ทธ์ค๋๋์ ์๊ฐ์ ํจ๊ณผ๋ฅผ ์ถ๊ฐํด๋ณผ๊น์?
+ ๋๋๊ทธ ์ ์ ํ๋ ํจ๊ณผ ๋ฃ๊ธฐ
case .began:
cv.beginInteractiveMovementForItem(at: indexPath)
UIView.animate(withDuration: 0.15) {
self.transform = CGAffineTransform(scaleX: 1.03, y: 1.03)
}
case .ended:
cv.endInteractiveMovement()
UIView.animate(withDuration: 0.15) {
self.transform = .identity
}
default:
cv.cancelInteractiveMovement()
UIView.animate(withDuration: 0.15) {
self.transform = .identity
}
+ ๋๋๊ทธ ์ค ๊ทธ๋ฆผ์ ํจ๊ณผ ์ถ๊ฐํ๊ธฐ
case .began:
UIView.animate(withDuration: 0.15) {
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOpacity = 0.25
self.layer.shadowRadius = 8
self.layer.shadowOffset = CGSize(width: 0, height: 3)
}
case .ended:
UIView.animate(withDuration: 0.15) {
self.layer.shadowOpacity = 0
self.transform = .identity
}
๋ชจ๋ ์ฝ๋๋ฅผ ์ถ๊ฐํด์ฃผ๋ฉด, ๊ฒฐ๊ณผ๋ ์๋ ๊ฒ ๋์ต๋๋ค~~!

'๐ถ๐ข๐ฆ > ๐ iOS ์ค์ ํธ' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| [iOS / Uikit] ๋คํธ์ํฌ ์ฐ๊ฒฐ์ ํด๋ณด์! (Moyaํธ) (0) | 2025.12.04 |
|---|