Data delivery with drag and drop
At a glance
| Item |
Summary |
| Purpose |
Transfers contact cards within the table or across apps as vCard and UTF-8 text item-provider representations. |
| App architecture |
The domain object implements item-provider read/write protocols; a table controller adapts drag/drop sessions to one data-source owner and commits asynchronous external loads into the table. |
| Main patterns |
Transferable model, multiple representations, framework delegate adapter, async placeholder/commit, local fast path, and extracted data source. |
| Project style |
Four Swift files: transferable model, table/drop coordinator, detail screen, and app lifecycle. |
Project structure
ClientList/
├── ContactCard.swift
├── ContactsTableViewController.swift
│ ├── ClientsDataSource
│ ├── UITableViewDropDelegate extension
│ └── UITableViewDragDelegate extension
├── ContactDetailsViewController.swift
├── AppDelegate.swift
└── Base.lproj/Main.storyboard
Structure observations
- Data encoding/decoding belongs to
ContactCard, not the drag delegate.
- Row storage and local moves belong to
ClientsDataSource.
- Drag/drop policy stays in same-file protocol extensions on the table controller.
Overall architecture
flowchart LR
Table["ContactsTableViewController"] --> Data["ClientsDataSource"]
Data --> Cards["[ContactCard]"]
Table -->|"drag row"| Provider["NSItemProvider"]
Cards --> Provider
Provider -->|"vCard or UTF-8"| OtherApp["Other app / drag system"]
OtherApp -->|"promised loadObject"| Drop["UITableViewDropCoordinator"]
Drop -->|"placeholder + commit"| Data
Drop -->|"local sourceIndexPath"| Move["moveClient"]
Move --> Data
Table -->|"segue injection"| Detail["ContactDetailsViewController"]
Reference code
ClientList/ContactCard.swift:34 — the domain object itself declares supported types and decodes either a vCard or a plain-text fallback into the same model.
static var readableTypeIdentifiersForItemProvider = [
kUTTypeVCard as String,
kUTTypeUTF8PlainText as String
]
static func object(
withItemProviderData data: Data,
typeIdentifier: String
) throws -> ContactCard {
let contact = ContactCard(name: "")
if typeIdentifier == kUTTypeVCard as String {
let values =
try CNContactVCardSerialization.contacts(with: data)
guard let value = values.first else {
throw ContactCardError.invalidVCard
}
contact.name = value.givenName + " " + value.familyName
} else if typeIdentifier == kUTTypeUTF8PlainText as String {
contact.name = String(data: data, encoding: .utf8)!
}
return contact
}
Interpretation
The transfer boundary is ContactCard, not a view controller. The table controller only decides move/copy/placement policy; NSItemProvider negotiates a representation and asks the model to encode or decode it.
Ownership and state
classDiagram
ContactsTableViewController *-- ClientsDataSource : strong let
UITableView --> ClientsDataSource : dataSource callback
ClientsDataSource *-- ContactCardArray : mutable clients
ContactCard *-- String : name and phone
ContactCard o-- UIImage : optional photo
UIDragItem *-- NSItemProvider : transfer promise
NSItemProvider o-- ContactCard : local source object
UITableViewDropPlaceholderContext --> ClientsDataSource : commit insertion
ContactDetailsViewController o-- ContactCard : injected IUO
Ownership evidence
ClientList/ContactsTableViewController.swift:55 — the table controller keeps one strong data-source owner and installs it for data, drag, and drop collaboration.
class ContactsTableViewController: UITableViewController {
let dataSource = ClientsDataSource()
override func viewDidLoad() {
super.viewDidLoad()
tableView.dropDelegate = self
tableView.dragDelegate = self
tableView.dataSource = dataSource
}
override func prepare(
for segue: UIStoryboardSegue,
sender: Any?
) {
let details =
segue.destination as! ContactDetailsViewController
details.contactCard =
dataSource.clients[tableView.indexPathForSelectedRow!.row]
}
}
| Owner |
Object or state |
Relationship |
Mutation authority |
ClientsDataSource |
Ordered clients array |
Strong model owner |
Local move and external-drop commit |
ContactsTableViewController |
Data-source instance |
Strong stable owner for table callbacks |
Controller initialization only |
| Table view |
Drag/drop delegates and row presentation |
Framework callback references |
UIKit interaction lifecycle |
UIDragItem / NSItemProvider |
Transfer promise/representations |
Framework-managed for drag session |
Provider load and model protocols |
| Placeholder context |
Pending external row insertion |
Temporary transaction |
commitInsertion closure |
| Detail controller |
Injected contactCard |
Strong session reference |
Set by source controller before display |
Class and protocol design
| Type or protocol |
Responsibility |
Depends on or conforms to |
ContactCard |
Contact values plus vCard/plain-text serialization |
NSItemProviderReading, NSItemProviderWriting |
ContactCardError |
Invalid type/vCard failure cases |
Error |
ClientsDataSource |
Own rows, bind cells, and perform synchronized local moves |
UITableViewDataSource |
ContactsTableViewController |
Negotiate drag/drop policy and async insertion |
UITableViewDragDelegate, UITableViewDropDelegate |
ContactDetailsViewController |
Present one injected contact |
UIViewController |
This is protocol-oriented integration even without an app-defined protocol: ContactCard conforms directly to two narrow data-transfer contracts, so the system can request representations without knowing the table/controller.
Access control
| Symbol |
Access |
Verified effect |
Why it fits this design |
ContactCard |
implicit internal final class |
Module-visible and cannot be subclassed. |
Fixes serialization behavior to one concrete transfer model. |
| Contact properties and serialization methods |
implicit internal |
Table/detail code and item-provider machinery can access them within target. |
Simple sample binding; production could use private setters. |
ClientsDataSource.clients |
implicit internal var |
Drop controller mutates the array directly. |
Enables concise placeholder commits but weakens encapsulation. |
Table controller’s dataSource |
implicit internal let |
Stable reference readable across module. |
Strong ownership is important; private let plus data-source methods would be narrower. |
Detail contactCard |
implicit internal IUO |
Source controller injects it before viewDidLoad. |
Storyboard/segue contract; nonoptional initializer injection would be safer programmatically. |
| Detail outlets |
weak, implicit internal |
View hierarchy retains controls. |
Correct storyboard ownership. |
Explicit private / fileprivate / public |
Not used |
No lexical/public library boundary. |
Sample prioritizes transfer flow over defensive access design. |
Reference code
ClientList/ContactCard.swift:60 — writing exposes the same two ordered representations and rejects unsupported identifiers explicitly.
static var writableTypeIdentifiersForItemProvider = [
kUTTypeVCard as String,
kUTTypeUTF8PlainText as String
]
func loadData(
withTypeIdentifier typeIdentifier: String,
forItemProviderCompletionHandler completion:
@escaping (Data?, Error?) -> Void
) -> Progress? {
if typeIdentifier == kUTTypeVCard as String {
completion(createVCard(), nil)
} else if typeIdentifier == kUTTypeUTF8PlainText as String {
completion(name.data(using: .utf8), nil)
} else {
completion(nil, ContactCardError.invalidTypeIdentifier)
}
return nil
}
Logic ownership and placement
| Logic |
Owning type or file |
Placement rationale |
| vCard/text encoding and decoding |
ContactCard |
Representation invariants travel with the transferable model. |
| Row storage/cell binding/local reordering |
ClientsDataSource |
One owner synchronizes array and table mutations. |
| Drag item construction and preview |
Drag-delegate extension |
Requires table index path and drag session context. |
| Move/copy/drop-intent policy |
Drop-delegate extension |
Framework asks the destination controller during tracking. |
| Async placeholder and insertion commit |
Drop controller + placeholder context |
Coordinates promised data with final UIKit index path. |
| Contact presentation |
Detail controller |
Owns labels/image view and navigation title. |
Design patterns
| Pattern |
Source evidence |
Purpose or tradeoff |
| Transferable model |
ClientList/ContactCard.swift:19 |
Makes domain data directly readable/writable by item providers. |
| Multiple representations |
ClientList/ContactCard.swift:36 |
Offers rich vCard first and interoperable text fallback. |
| Drag/drop adapter |
ClientList/ContactsTableViewController.swift:97 |
Translates UIKit session callbacks into model/table actions. |
| Local fast path |
ClientList/ContactsTableViewController.swift:113 |
Reorders existing data without serializing/loading it. |
| Async placeholder transaction |
ClientList/ContactsTableViewController.swift:131 |
Reserves UI position, then commits when promised data arrives. |
| Extracted data source |
ClientList/ContactsTableViewController.swift:12 |
Separates ordered storage/cell binding from interaction policy. |
| Constructor through system protocol |
ClientList/ContactCard.swift:39 |
Lets NSItemProvider create the model from negotiated bytes. |
Naming conventions
- Domain and screen roles are explicit:
ContactCard, ClientsDataSource, ContactsTableViewController, and ContactDetailsViewController.
- Transfer methods keep framework-required names; internal commands use
moveClient and createVCard.
clients names the mutable collection while client(index:) names one lookup.
- Error cases name invalid inputs directly:
invalidTypeIdentifier and invalidVCard.
Architecture takeaways
- Put representation negotiation/serialization on the transferable model, not in UI delegates.
- Use the drag/drop controller for policy and the data source for ordered model mutation.
- Separate local moves from cross-app promised loads.
- Use placeholder commit APIs so asynchronous data and final table indices stay synchronized.
- Narrow mutable array access in production even when direct mutation keeps a sample short.
Source map
| Source file |
Relevant symbols |
ClientList/ContactCard.swift |
Transferable model, representations, vCard codec |
ClientList/ContactsTableViewController.swift |
Data source, drag/drop policy, async insertion |
ClientList/ContactDetailsViewController.swift |
Detail injection and display |
ClientList/AppDelegate.swift |
Application/window lifecycle |