🐙
SwiftUI copyable, cuttable, pasteDestinationの使い方
copyable, cuttable, pasteDestination
copyable, cuttable, pasteDestinationの3つは、macOS 13.0(Ventura)
から利用できるClipboardに関するViewModifierです。
キーボード操作に関わるものなので、iOS/ tvOS/ watchOSなどにはありません。(iPadがあるので、iOSでもそのうち有効になるかも?)
前提知識
以下も知っておくとよりわかりやすい
今までにあった機能(古くなったもの)
以下はNSItemProviderを使用することでコピー、カット、ペーストを実現していました。
SwiftUIではNSItemProviderをTransferable
へ移行しようとする動きがあります。
copyable(_:)
Command-C
が入力された際に、Transferable
に準拠しているアイテムの配列を渡します。
基本的には選択された
複数のアイテムをコピーします。
.copyable(birds.filter { selection.contains($0.id) })
cuttable(for:action:)
Command-X
が入力された際に、Transferable
に準拠しているアイテムの配列を渡します。
基本的には選択された
複数のアイテムをカットします。
カットしているので、全体のデータからカットした文を削除しつつ、コピーする必要があります。
.cuttable(for: Bird.self) {
let birds = birds.filter { selection.contains($0.id) }
self.birds.removeAll { selection.contains($0.id) }
return birds
}
pasteDestination(for:action:validator:)
Command-V
が入力された際に、Transferable
に準拠しているアイテムの配列を受け取ります。
ここでは文字列を渡された想定です。
渡された文字列を元に、Birdを作成して、データに追加しています。
validator
では渡されたアイテムで有効なアイテムのみを返す(filter)ようにします。指定しなければ全て利用されます。
// 文字列のコピーをペーストする
.pasteDestination(for: String.self) { names in
let newBirds: [Bird] = names.map { .init(id: .init(), name: $0) }
birds.append(contentsOf: newBirds)
} validator: { names in
// validate(有効)な文字列のみペーストするように
names.filter { knownBirds.contains($0) }
}
全体のコード
import SwiftUI
struct Bird: Identifiable, Codable, Transferable {
let id: UUID
let name: String
static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(contentType: .data)
}
}
struct ContentView: View {
@State private var birds: [Bird] = [
.init(id: .init(), name: "owl"),
.init(id: .init(), name: "sparrow"),
.init(id: .init(), name: "robin")
]
@State private var selection: Set<UUID> = []
let knownBirds = [
"owl",
"parrot",
"swift",
"sparrow",
"robin",
"bluebird"
]
var body: some View {
List(birds, selection: $selection) {
Text($0.name)
.tag($0.id)
}
.copyable(birds.filter { selection.contains($0.id) })
.cuttable(for: Bird.self) {
let birds = birds.filter { selection.contains($0.id) }
self.birds.removeAll { selection.contains($0.id) }
return birds
}
.pasteDestination(for: Bird.self) { birds in
let newBirds: [Bird] = birds.map { .init(id: .init(), name: $0.name) }
self.birds.append(contentsOf: newBirds)
}
// 文字列のコピーをペーストする
.pasteDestination(for: String.self) { names in
let newBirds: [Bird] = names.map { .init(id: .init(), name: $0) }
birds.append(contentsOf: newBirds)
} validator: { names in
// validate(有効)な文字列のみペーストするように
names.filter { knownBirds.contains($0) }
}
}
}
Discussion