Thomas Debouverie
Thomas Debouverie

Reputation: 283

iOS 11 drag & drop: dropping on standard Calendar app

I'm trying to integrate the iOS 11 drag&drop feature in an app, from Xcode 9 beta. I'm interesting on creating a NSItemProvider that can be understood by the standard calendar app. My drag starts from a UITableView, so only a single dragDelegate method implemented.

So far, I have tried the following:

    let text = "Rendez-vous at \(clientName)"
    let data = text.data(using: .utf8)

    let itemProvider = NSItemProvider()
    itemProvider.registerDataRepresentation(forTypeIdentifier: kUTTypeCalendarEvent as String, visibility: .all) { completion in
        completion(data, nil)
        return nil
    }

    let dragItem = UIDragItem(itemProvider: itemProvider)

And also tried by using the type identifier kUTTypePlainText. No luck, calendar app does not register the drop.

I can't find any official documentation about this. I'm hoping the calendar app is looking for some standard calendar data, and this is not limited to standard apps communicating with each other. For example, you can drag text from the notes app and drop it in calendar to create an event.

Anyone knows what I could try ?

Thanks in advance.

Upvotes: 3

Views: 947

Answers (1)

Lucas
Lucas

Reputation: 880

TL;DR: The secret ingredients to get the Default iOS Calendar app to recognize your custom object conforming to the NSItemProviderWriting protocol are the "com.apple.ical.ics" type identifier and exporting your custom object as a iCalendar string (vEvent).


I've been playing around with this for a few days and I finally managed to get it to work! I'll try and break down my steps as much as possible.

  1. Create a custom object that conforms to NSItemProviderWriting:
    • To conform to the protocol, you'll need to implement writableTypeIdentifiersForItemProvider and loadData(withTypeIdentifier:, forItemProviderCompletionHandler:).
    • The array of identifiers are a list of the type identifiers that you can export, in fidelity order, with highest fidelity first. So put your custom type identifier first and include "com.apple.ical.ics" after.
    • The loadData function, mentioned above, will attempt to find an identifier that the drag destination (the Calendar app in this scenario) supports and that your app also supports, trying to get the highest fidelity first.

For example:

public static var writableTypeIdentifiersForItemProvider: [String] {
    return ["com.yourcompany.myEvent", "com.apple.ical.ics"]
}

public func loadData(withTypeIdentifier typeIdentifier: String, forItemProviderCompletionHandler completionHandler: @escaping (Data?, Error?) -> Void) -> Progress? {
    switch typeIdentifier {
    case "com.yourcompany.myEvent": // Your custom handling goes here
    case "com.apple.ical.ics": completionHandler(createVEvent(), nil)
    default: completionHandler(nil, YourAppError.someCustomError)
    }

    return nil
}
  1. Create an iCalendar vEvent string with your custom event information:
    • This step is very similar to the demo in Session 227 from WWDC 2017, where they create a vCard string for a custom contact. Checkout the demo project from that session.
    • Essentially, the Calendar app supports a few specific types of type identifiers and the iCal format is one of them. Therefore, you just need to make a helper method to turn your custom event type into a iCalendar string.
    • I won't go into the details on vEvents but there's much good information here and here.

Here's a suggestion:

private func createVEvent() -> Data? {
    let today = Date()

    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss'Z'"
    dateFormatter.timeZone = TimeZone(abbreviation: "UTC")

    let startDateString = dateFormatter.string(from: startDate)
    let endDateString = dateFormatter.string(from: endDate)
    let todayString = dateFormatter.string(from: today)

    var vCalendarText = "BEGIN:VCALENDAR\n"
    vCalendarText +=    "VERSION:2.0\n"
    vCalendarText +=    "PRODID:-//Your Company//App Name//EN\n"
    vCalendarText +=    "BEGIN:VEVENT\n"

    vCalendarText +=    "UID:\(identifier.uuidString)\n"
    vCalendarText +=    "SUMMARY:\(name)\n"
    vCalendarText +=    "DTSTAMP:\(todayString)\n"
    vCalendarText +=    "DTSTART:\(startDateString)\n"
    vCalendarText +=    "DTEND:\(endDateString)\n"

    vCalendarText +=    "END:VEVENT\n"
    vCalendarText +=    "END:VCALENDAR"

    return vCalendarText.data(using: .utf8)
}

↑ Similar to the createVCard() function from that session demo I mentioned earlier…

That's it! That's pretty much all you need. The hard part is knowing what identifier the Calendar app supports and how to make a vEvent. Thanks to quiker for finding the supported identifiers.

One last tip: You can validate your iCalendar string with this nifty tool here.

Notice that this only allows for dropping your custom object. Dragging an event from the calendar to your app is a different story, but you can figure it out from my tips here. :)

Happy coding!

Upvotes: 7

Related Questions