Sponsor Link
Environment
- macOS Big Sur 11.2.3
- Xcode 12.4
[SwiftUI] SwiftUI で実装する Drag&Drop (その1: String を Drag&Drop)
[SwiftUI] SwiftUI で実装する Drag&Drop (その2:クラスオブジェクト を Drag&Drop)
define own UTI type
First let’s define own UTI type with using extension.
extension UTType {
static var myOwnType: UTType {
UTType(exportedAs: "com.mycompany.myowntype")
}
}
Usually reverese DNS is recommended for new UTIs.
There are two options for defining UTI. one is “ExportedAs”, another is “ImportedAs”.
For your own UTI, need to use “ExportedAs”. this means you are the owner of the UTI.
“ImportedAs” will be used for “public.text” for example. that means another company/person owns the UTI, but your app can handle the type.
for detail, please refer to the apple doc here.
Define UTI type again (in Info.plist)
Because OS also needs to understand the UTIs which will be handled by application.
You need to put UTI information in Info.plist.
Conveniently Xcode provide the GUI for maintaining the UTI.
- Click target (for the application)
- Click Info
- define identifier (com.mycompany.myowntype in this case)
- need to conforms to public.item (or subclass of it) for Drag and drop (public.item in this case)
Followings are XML expression, so you can edit Info.plist manually.
UTExportedTypeDeclarations UTTypeConformsTo public.item UTTypeIcons UTTypeIdentifier com.mycompany.myowntype UTTypeTagSpecification
Note1: Xcode will check the definition at compile time
In case you define your own UTI only in code, Xcode will give the warning.
Note2: UTI needs to conform physical hierarchy for Drag&Drop
If want to use in Drag and Drop, new UTI needs to conform to one of the physical hierarchy type (like public.item).
Note3: conformation information should be defined in Info.plist
Above conformance information should be defined in Info.plist (for Drag&Drop). Otherwise does not work.
Use own UTI type
Just use it as one of UTType.
Use new own UTI type in MyDropData class
Here is the code.
extension UTType {
static var myOwnType: UTType {
UTType(exportedAs: "com.mycompany.myowntype")
}
}
final class MyDropData: NSObject, NSItemProviderReading, NSItemProviderWriting {
var str: String
init(_ str:String) {
self.str = str
}
static var readableTypeIdentifiersForItemProvider: [String] { [UTType.myOwnType.identifier] }
static func object(withItemProviderData data: Data, typeIdentifier: String) throws -> MyDropData {
guard typeIdentifier == UTType.myOwnType.identifier else { throw MyError.unknownType }
if let str = String(data: data, encoding: .utf8) {
return MyDropData(str)
}
throw MyError.generateError
}
static var writableTypeIdentifiersForItemProvider: [String] { [UTType.myOwnType.identifier] }
func loadData(withTypeIdentifier typeIdentifier: String, forItemProviderCompletionHandler completionHandler: @escaping (Data?, Error?) -> Void) -> Progress? {
let data = str.data(using: .utf8)
completionHandler(data, nil)
return nil
}
}
just use own UTI instead of kUTTypeUTF8PlainText.
Note: In some cases, may need to use own type as String. we can use identifier static method of UTType for such cases.
Use new own UTI in SwiftUI
As you see, basically we don’t need to change the code. because MyDropData class handles type information as well.
struct ContentView: View {
@State private var dragText2 = "Drag from"
@State private var dropText2 = "Drop to"
@State private var isTarget2 = false
var body: some View {
HStack {
List {
Text(dragText2)
.onDrag {
return NSItemProvider(object: MyDropData(dragText2))
}
}
List {
Text(dropText2)
.onDrop(of: MyDropData.readableTypeIdentifiersForItemProvider, isTargeted: $isTarget2) { providers -> Bool in
guard let provider = providers.first else { return false }
guard provider.canLoadObject(ofClass: MyDropData.self) else { return false }
provider.loadObject(ofClass: MyDropData.self) { (data, error) in
if let myDropData = data as? MyDropData {
dropText2 += " \(myDropData.str)"
}
}
return true
}
}
}
}
}
code
import SwiftUI
import UniformTypeIdentifiers
enum MyError: Error {
case unknownType
case generateError
}
extension UTType {
static var myOwnType: UTType {
UTType(exportedAs: "com.mycompany.myowntype")
}
}
final class MyDropData: NSObject, NSItemProviderReading, NSItemProviderWriting {
var str: String
init(_ str:String) {
self.str = str
}
static var readableTypeIdentifiersForItemProvider: [String] { [UTType.myOwnType.identifier] }
static func object(withItemProviderData data: Data, typeIdentifier: String) throws -> MyDropData {
guard typeIdentifier == UTType.myOwnType.identifier else { throw MyError.unknownType }
if let str = String(data: data, encoding: .utf8) {
return MyDropData(str)
}
throw MyError.generateError
}
static var writableTypeIdentifiersForItemProvider: [String] { [UTType.myOwnType.identifier] }
func loadData(withTypeIdentifier typeIdentifier: String, forItemProviderCompletionHandler completionHandler: @escaping (Data?, Error?) -> Void) -> Progress? {
let data = str.data(using: .utf8)
completionHandler(data, nil)
return nil
}
}
struct ContentView: View {
@State private var dragText2 = "Drag from"
@State private var dropText2 = "Drop to"
@State private var isTarget2 = false
var body: some View {
HStack {
List {
Text(dragText2)
.onDrag {
return NSItemProvider(object: MyDropData(dragText2))
}
}
List {
Text(dropText2)
.onDrop(of: MyDropData.readableTypeIdentifiersForItemProvider, isTargeted: $isTarget2) { providers -> Bool in
guard let provider = providers.first else { return false }
guard provider.canLoadObject(ofClass: MyDropData.self) else { return false }
provider.loadObject(ofClass: MyDropData.self) { (data, error) in
if let myDropData = data as? MyDropData {
dropText2 += " \(myDropData.str)"
}
}
return true
}
}
}
}
}
Summary:how to handle my own UTI in Drag&Drop
- need to define UTI in code. use “ExportedAs” in case you’re the owner of the UTI. if not, use “ImportedAs” instead.
- need to define UTI in Info.plist too.
- for using in Drag&Drop, the UTI needs to conform one of the physical type (i.e. descendants of public.item)
Thank you for reading. Hope you enjoy this post.
please feel free to contact to twitter.
Sponsor Link