SwiftUI에서 Drag & Drop 적용하기
안녕하세요. 그린입니다 🍏
이번 포스팅에서는 SwiftUI로 Drag & Drop을 구현해보겠습니다 🙋🏻
우선, SwiftUI에서 draggable과 dropDestination이라는 메서드가 존재해요.
이걸 알아보고 활용해보려 합니다!
그럼 바로 슛~ 🔫
draggable
iOS 16에서 부터 나온 이 메서드는 뷰를 드래그 앤 드롭 가능한 소스로 활성화 시켜줍니다.
func draggable<T>(_ payload: @autoclosure @escaping () -> T) -> some View where T : Transferable
정의는 이러하며, payload라는 파라미터가 존재합니다.
이 파라미터는 단일 인스턴스 혹은 이 뷰에 드래그 가능한 데이터를 나타내는 Transferable한 값을 반환하는 클로저에요.
즉, 해당 뷰 모디파이어를 적용시키면 해당 뷰는 이제 드래그 앤 드롭 작업을 할 수 있도록 활성화 되는것이죠!
간단한 사용을 위해 코드 예시를 볼까요?
import SwiftUI
struct ContentView: View {
var content: String = "green"
var body: some View {
Color.green
.frame(width: 200, height: 200)
.draggable(content)
}
}
이렇게 초록색 사각형을 드래그 꾹 눌러 드래그를 할때 green이라는 String을 담아 이동시킬 수 있는것이죠.
한번 볼까요?
draggable을 사용하지 않으면 꾹 눌러도 드래그할 수 없지만, 사용함으로 이렇게 해당 콘텐츠를 드래그할 수 있게 인식하고 제공하죠.
그런데, 간혹 드래그 도중에 저렇게 해당 뷰가 아닌 다른 뷰로 보여지도록도 사용할 수 있어요.
func draggable<V, T>(
_ payload: @autoclosure @escaping () -> T,
@ViewBuilder preview: () -> V
) -> some View where V : View, T : Transferable
위에서의 이니셜라이저가 아닌, preview 파라미터가 담긴 이니셜라이저를 사용하는것입니다.
이 파라미터에는 드래그 도중에 어떤 뷰를 미리보기 소스로 사용할지를 나타내는것입니다.
한번 코드로 적용해볼까요?
import SwiftUI
struct ContentView: View {
var content: String = "green"
var body: some View {
Color.green
.frame(width: 200, height: 200)
.draggable(content) {
Text(content)
.background(Color.green)
}
}
}
기존 코드에서 단순히 클로저로 미리보기로 보여질 뷰를 만들어주면 되는것이죠!
그럼 이렇게 동작하게 됩니다.
아주 간단하죠!?
이렇게 해당 뷰를 드래그 가능한 뷰로 만들고 미리보기까지 적용시켜줄 수 있어요.
근데, 사실 이렇게 드래그만 하는건 의미가 없겠죠?
당연히, drop까지 시켜봐야합니다!
그래서 바로 drop하는 방법에 대해 알아볼께요 😃
dropDestination
draggable과 마찬가지로 iOS 16에서부터 사용할 수 있어요.
지정한 클로저를 사용해 드롭된 컨텐츠를 처리하도록 적용할 수 있습니다.
func dropDestination<T>(
for payloadType: T.Type = T.self,
action: @escaping ([T], CGPoint) -> Bool,
isTargeted: @escaping (Bool) -> Void = { _ in }
) -> some View where T : Transferable
정의를 보시면, payloadType 파라미터는 삭제된 모델의 예상 타입을 나타내요.
즉, 드래그로 넘겨받아 드롭될때 타입이 같아야겠죠?
그리고, 두번째 action 클로저 파라미터가 존재하는데요.
여기서는 두개의 클로저 매개변수가 있어요.
첫번째는 해당 드래그된 아이템이고, 두번째는 드롭된 뷰의 위치를 나타내줍니다.
그리고 마지막으로 isTargeted 파라미터는 드래그 앤 드롭 작업이 드롭 대상 영역에 들어가거나 나갈 때 호출되는 클로저입니다.
즉, 커서 영역 내부에 존재할때는 해당 값은 true일것이고, 커서 영역 밖에서는 false겠죠.
그럼 한번 사용 예시를 볼까요?
import SwiftUI
struct ContentView: View {
var content: String = "green"
@State private var isDropTargeted: Bool = false
var body: some View {
VStack(spacing: 50) {
Color.green
.frame(width: 200, height: 200)
.draggable(content) {
Text(content)
.background(Color.green)
}
Text("Drop Here")
.font(.title)
.frame(width: 200, height: 200)
.border(.black)
.dropDestination(for: String.self) { receivedTitles, location in
animateDrop(at: location)
process(titles: receivedTitles)
return true
} isTargeted: {
isDropTargeted = $0
}
}
}
func process(titles: [String]) {
// 드롭 시 데이터를 이용한 후속 처리
}
func animateDrop(at: CGPoint) {
// 드롭 시 위치를 이용한 후속 처리
}
}
이런식으로 dropDestination을 이용해 원하는 뷰에 붙여줄 수 있습니다.
drop 시, action 파라미터에서 데이터와 드롭된 위치를 이용해 후속 처리를 해줄 수 있으며 action은 항상 Bool 타입을 반환해야 합니다.
한번 어떤 느낌인지 돌려볼께요.
초록 사각형을 드래그 할때 다른 위치에서는 우측 상단 + 뱃지가 안나오다가 해당 Drop Here 텍스트에 위치하니 + 뱃지가 나오는걸 볼 수 있어요.
즉, 해당 뷰에만 dropDestination이 적용되어 있기에 그렇습니다.
해당 영역에 드롭할 수 있다는 소리죠.
좀 더 나아가서 실제 유의미하게 drop까지 구현해볼께요.
import SwiftUI
struct ContentView: View {
var content: String = "green"
@State private var dropText: String = "Drop Here"
@State private var isDisplayGreenRectangle: Bool = true
var body: some View {
VStack(spacing: 50) {
if isDisplayGreenRectangle {
Color.green
.frame(width: 200, height: 200)
.draggable(content) {
Text(content)
.background(Color.green)
}
}
Text(dropText)
.font(.title)
.frame(width: 200, height: 200)
.border(.black)
.background(isDisplayGreenRectangle ? .white : .green)
.dropDestination(for: String.self) { receivedTitles, location in
process(titles: receivedTitles)
return true
}
}
}
func process(titles: [String]) {
withAnimation {
dropText = titles.first ?? "What"
isDisplayGreenRectangle.toggle()
}
}
}
간단히 로직 테스트를 해보고 싶은건, 초록 사각형을 드래그하여 Drop Here 공간에 두면 해당 초록 사각형 뷰는 사라지고, Drop Here의 텍스트는 green으로 바뀌며 백그라운드 색상도 초록색으로 바꿔보는것이죠.
현재 사용하지 않는 location에 대한 처리나 isTargeted 처리도 없앴습니다.
즉, 정말 드래그가 되어 데이터와 뷰가 변하는것처럼 해보는것입니다.
한번 볼까요?
잘 동작하죠?
이런 기본 흐름을 이해하면 드래그 앤 드롭을 커스텀하게 입맛대로 구현해볼 수 있어요.
필요하다면, location까지 적용시켜볼 수 있죠.
마무리
이렇게 우선 SwiftUI에서 드래그 앤 드롭을 위해 draggable과 dropDestination을 알아보고 간단히 활용해봤어요!
근데 iOS 16부터 사용가능 하다는것도 있고, 사실 onDrag와 onDrop으로도 구현이 가능하기에 다음 포스팅에서는 onDrag와 onDrop을 살펴보도록 하겠습니다 🙋🏻