よくある UI の挙動を SwiftUI でどれだけシンプルに作れるのか。
やってみました。
import SwiftUI | |
struct Fruit: Identifiable, Equatable { | |
let id = UUID() | |
let name: String | |
let color: Color | |
} | |
struct DraggableList: View { | |
@State private var fruits: [Fruit] = [ | |
Fruit(name: "APPLE", color: .red), | |
Fruit(name: "ORANGE", color: .orange), | |
Fruit(name: "BANANA", color: .yellow), | |
Fruit(name: "MELON", color: .green), | |
Fruit(name: "PEACH", color: .pink), | |
Fruit(name: "KIWI", color: .brown), | |
Fruit(name: "GRAPES", color: .purple), | |
Fruit(name: "LIME", color: .cyan), | |
Fruit(name: "TOMATO", color: .indigo) | |
] | |
@State private var dragging: Fruit? | |
var body: some View { | |
ScrollView { | |
LazyVStack(spacing: 16) { | |
ForEach(fruits) { fruit in | |
FruitItemView(fruit: fruit) | |
.onDrag { | |
print("onDrag: \(fruit)") | |
dragging = fruit | |
//return NSItemProvider() // iOS only | |
return NSItemProvider(object: NSString(string: "\(fruit.id)")) | |
} preview: { | |
//EmptyView() // crash on macOS | |
PreviewView() | |
} | |
.onDrop( | |
of: [.text], // * | |
delegate: DropViewDelegate( | |
fruit: fruit, | |
fruits: $fruits, | |
dragging: $dragging | |
) | |
) | |
} | |
} | |
} | |
.padding() | |
} | |
} | |
struct FruitItemView: View { | |
var fruit: Fruit | |
var body: some View { | |
HStack { | |
Spacer() | |
Text("\(fruit.name)") | |
Spacer() | |
} | |
.foregroundStyle(.background) | |
.padding(.vertical, 40) | |
.background(fruit.color, in: .rect(cornerRadius: 16)) | |
} | |
} | |
struct PreviewView: View { | |
var body: some View { | |
EmptyView() | |
} | |
} | |
struct DropViewDelegate: DropDelegate { | |
let fruit: Fruit | |
@Binding var fruits: [Fruit] | |
@Binding var dragging: Fruit? | |
func dropEntered(info: DropInfo) { | |
print("\(dragging?.name ?? "") on \(fruit.name)") | |
if dragging != fruit { | |
let from = fruits.firstIndex(of: dragging!)! | |
let to = fruits.firstIndex(of: fruit)! | |
withAnimation { | |
fruits.move( | |
fromOffsets: IndexSet(integer: from), | |
toOffset: to > from ? to + 1 : to | |
) | |
} | |
} | |
} | |
func dropUpdated(info: DropInfo) -> DropProposal? { | |
return DropProposal(operation: .move) // icon on macOS | |
} | |
func performDrop(info: DropInfo) -> Bool { | |
print("performDrop: \(info)") | |
dragging = nil | |
return true | |
} | |
} | |
#Preview { | |
DraggableList() | |
} |
本来は、何かを NSItemProvider()
経由で、
ドロップ先に渡すのが役目っぽいけども、
DropDelegate
の便利さを利用して
配列を並び替えるイメージ。
並び替えのアニメーションは withAnimation
デフォルトに頼る。
iOS と macOS、Preview と シュミレータ と 実機、OS バージョンなど、
互換しようとするといろいろありそう。
ここらのコンポーネントはまだ不安定な感じ ?
【SwiftUI】シンプルにドラッグで並び替えできる List を 作りたい #Swift #プログラミング #エンジニア
https://t.co/dsv82ASZrR pic.twitter.com/GWgyulTaim
— chanzmao (@maochanz) September 11, 2024