4.2 把视图变成拖拽源

原文链接: https://developer.apple.com/documentation/swiftui/making-a-view-into-a-drag-source

4.2 把视图变成拖拽源

采用 draggable API 为拖放操作提供项目。

概述

当有人拖拽你应用中某个项目在屏幕上的视觉表示(例如一张照片、一个地图位置、一个日历事件或一段选中的文本)时,这次拖拽操作会关联一些数据,以及系统显示的该数据的预览。添加 draggable(_:) 修饰符即可让该视图充当拖拽源,并提供一个遵循 Transferable 协议的值。

Transferable 协议描述了如何为共享和数据传输而序列化与反序列化你的模型对象。它通过组合 Core Transferable 框架中一个或多个内置 TransferRepresentation 类型来提供传输表示。

让视图充当拖拽源

使用 draggable(_:) 修饰符可以在应用内、你自己的多个应用之间,或者在你的应用与支持导入或导出某种数据格式的其他应用之间发送或接收 Transferable 项目。

1
2
3
4
5
6
7
8
struct MyView: View {
    let name = "Mei Chen"
    
    var body: some View {
        Text(name)
            .draggable(name)
    }
}

使用 draggable(_:preview:) 修饰符为被拖拽的项目定义一个自定义预览。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    Text(name)
        .draggable(name) {
            ZStack {
                RoundedRectangle(cornerRadius: 10)
                    .frame(width: 300, height: 300)
                    .foregroundStyle(.yellow.gradient)
                Text("Drop \(name)")
                    .font(.title)
                    .foregroundStyle(.red)
            }
        }

要定制系统在过渡到显示你的自定义 preview 时所显示的抬起预览,请应用带 dragPreview 类型的 contentShape(::eoFill:) 修饰符。例如,你可以像下面的代码示例那样更改预览的圆角半径:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    Text(name)
        .contentShape(.dragPreview, RoundedRectangle(cornerRadius: 7))
        .draggable(name) {
            ZStack {
                RoundedRectangle(cornerRadius: 20)
                    .frame(width: 300, height: 300)
                    .foregroundStyle(.yellow.gradient)
                Text("Drop \(name)")
                    .font(.title)
                    .foregroundStyle(.red)
            }
        }

为拖放操作创建可传输项目

要支持对模型对象的拖拽操作,请让模型遵循 Transferable 协议以创建可传输项目,并实现 TransferRepresentation 静态属性。String、Data、URL 和 Image 等类型已经遵循 Transferable,因此很容易在拖放操作中使用。

定义一个表示用户资料的数据模型,它是一个遵循 Codable 的类型,带有 name 和 phoneNumber 两个属性。

1
2
3
4
5
struct Profile: Codable, Identifiable {
    var id: UUID = UUID()
    var name: String
    var phoneNumber: String
}

为 Profile 扩展以遵循 Transferable 协议,从而组合出一个传输表示,并加入带自定义统一类型标识符 com.example.profile 的 CodableRepresentation 来表示 Profile 数据结构。

1
2
3
4
5
6
extension Profile: Transferable {
    static var transferRepresentation: some TransferRepresentation {
        CodableRepresentation(contentType: .profile)
        ProxyRepresentation(exporting: \.name)
    }
}

请务必在应用的 Info.plist 文件中包含这个自定义统一类型标识符。更多信息请参阅 为你的应用定义文件与数据类型。

在 UTType 上把新的统一类型标识符声明为便捷变量。对于导出的声明,请使用下面的代码:

1
2
3
extension UTType {
    static var profile = UTType(exportedAs: "com.example.profile")
}

前面代码示例中的另一种传输表示是 ProxyRepresentation,它使用 String 结构体内置的 Transferable 遵循。ProxyRepresentation 充当一种替代方案,让大家可以在任何不支持 com.example.profile 内容类型、但支持文本格式的文本编辑器中拖放资料项目。

要为某个可拖拽项目添加更多传输表示,请按优先级顺序指定一个或多个附加表示。这样可以确保系统根据接收方在拖放操作中能够接受的内容类型,使用最合适的表示。更多信息请参阅 为模型类型选择传输表示。

要把 com.example.profile 内容类型用于拖拽操作,请把各个资料传入 draggable(_:) 修饰符。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
struct ContentView: View {
    @State private var profiles = [
        Profile(name: "Juan Chavez", phoneNumber: "(408) 555-4301"),
        Profile(name: "Mei Chen", phoneNumber: "(919) 555-2481")
    ]
    
    var body: some View {
        List {
            ForEach(profiles) { profile in
                Text(profile.name)
                    .draggable(profile)
            }
        }
    }
}

启用列表重新排序

在 List 中,你可以用 onMove(perform:) 修饰符启用重新排序。该修饰符允许你通过长按某一行并把它拖到新位置来重新排序列表项目。把 onMove(perform:) 修饰符添加到 List 上即可启用这一交互。

1
2
3
4
5
6
7
8
9
    List {
        ForEach(profiles) { profile in
            Text(profile.name)
        }
        .onMove { indices, newOffset in
            // 根据来源和目标索引更新 items 数组。
            profiles.move(fromOffsets: indices, toOffset: newOffset)
        }
    }

要有条件地停用某一行的项目重新排序,请把 moveDisabled(_:) 设为 true。