قالب وردپرس درنا توس
Home / Apple / Drag and drop guide for iOS

Drag and drop guide for iOS



Apple introduced drag and drop functionality in iOS 11, allowing users to drag items from one screen to another. On the iPhone, drag and drop is only available in one app, while on iPads it is also available across apps. This is very useful for doing things like quickly adding photos from Photos to an email.

In this tutorial you will explore drag and drop by building on CacheManager two apps for managing geocaches:

CacheMaker organizes geocaches into a Kanban board that is running and completing things. CacheEditor allows users to edit the details of a geocache retrieved from CacheMaker . To implement these management features, add drag and drop support for both apps.

Getting Started

Use the Download Materials button at the top or bottom of this tutorial to download the startup project. Open CacheManager.xcworkspace in Xcode and select CacheMaker as the active scheme:

Build and Run CacheMaker . You should see two aggregate views of the first containing geocaches for ongoing work:

Try dragging a geocache from the current field to the completed path:

Your goal with this The first part of the training is to make this work. Later you will unlock the ability to drag and drop geocaches to and from the CacheEditor companion app.

Take a look at the key CacheMaker files in Xcode: [19659010] CachesDataSource.swift : Represents the data source for a collection view of geocaches.

  • CachesViewController.swift : Displays the Kanban board for the geocaches.
  • These are the files you want to work on to add the desired functionality.

    Drag and Drop Overview

    When you drag items from a source app a drag activity begins and the system creates a drag session . The source app sets up a drag element to represent the underlying data when drag activity starts. Dropping the items in a destination app ends the drag activity.

    Drag elements are packed into a item supplier that describes the data types the source app can provide. When the items are dropped, the destination app asks for the items in the format it can consume.

    Apple automatically supports drag and drop text views and text fields. It also provides specialized APIs for table views and aggregate views. You can also add drag and drop to custom views.

    In this tutorial you will explore drag and drop in aggregate views and custom views.

    Add drag support

    Go to CachesDataSource.swift and add the following extension to the end of the file:

      CachesDataSource extension {
    func dragItems (for indexPath: IndexPath) -> [UIDragItem] {
    la geocache = geocache [indexPath.item]
    la itemProvider = NSItemProvider (object: geocache.name as NSString)
    light dragItem = OUTPUTItem (itemProvider: itemProvider)
    return [dragItem]
    }
    }
    

    Here you create a goods supplier from the NSString representation of the geocache name. You then return a group with one drag item that packs this provider.

    Next, open CachesViewController.swift and add the following to the end of the file:

      extension CachesViewController: UICollectionViewDragDelegate {
    func collectionView (_ collectionView: UICollectionView,
    Items For Beginning Session: EXTRACT Session,
    on indexPath: IndexPath) -> [UIDragItem] {
    la dataSource = dataSourceForCollectionView (collectionView)
    return dataSource.dragItems (for: indexPath)
    }
    }
    

    You adopt UICollectionViewDragDelegate and implement the required method called when a drag activity starts. Your implementation gets the data source for the collection view, and then returns the associated killings for the selected item.

    Add the following to viewDidLoad () after the delegation task for the collection view:

      collectionView. dragDelegate = self
    

    This makes the view controller the drag delegate.

    Build and run the app. Tap and hold on a viewport cell that represents a geocache. The dropped cell should rise so you can drag it around:

    Note that even if you can drag an item around, you can't drop it anywhere. Attempts to do so simply release it back where it started.

    Open Reminders in Split View next to CacheMaker . You should be able to drag a geocache and drop it into Reminders :

    Reminders can accept the exported NSString representation of the geocache- name and use it to create a new reminder.

    Now try to drag text from Reminders into CacheMaker . Nothing happens. That's because you haven't added drop support to CacheMaker . You are going to grab this next.

    Adding Drop Support

    Go to CachesDataSource.swift and add the following to CachesDataSource extension:

      func addGeocache (_ newGeocache: Geocache, by index: ) {
    geocaches.insert (newGeocache, at: index)
    }
    

    This adds a new geocache to the data source.

    Switch to CachesViewController.swift and add the following protocol extension to the end:

      extension CachesViewController: UICollectionViewDropDelegate {
    func collectionView (
    _ collectionView: UICollectionView,
    performDropWith coordinator: UICollectionViewDropCoordinator) {
    // 1
    la dataSource = dataSourceForCollectionView (collectionView)
    // 2
    let destinationIndexPath =
    IndexPath (item: collectionView.numberOfItems (inSection: 0), section: 0)
    // 3
    la item = coordinator.pubjects [0]
    // 4
    swap coordinator.proposal.operation
    {
    case. copy:
    print ("Copying ...")
    la itemProvider = item.dragItem.itemProvider
    // 5
    itemProvider.loadObject (ofClass: NSString.self) {string, error in
    if let string = string like? String {
    // 6
    la geocache = Geocache (
    name: string, summary: "Unknown", latitude: 0.0, longitude: 0.0)
    // 7
    dataSource.addGeocache (geocache, at: destinationIndexPath.item)
    // 8
    DispatchQueue.main.async {
    collectionView.insertItems (kl: [destinationIndexPath])
    }
    }
    }
    default:
    return
    }
    }
    }
    

    Here you use the UICollectionViewDropDelegate protocol. Then, implement the required method called when the user ends a drag activity. Your implementation:

    1. Gets the data source for the assembly view.
    2. Specifies the end of the collection view as the object drop destination.
    3. Selects the first drag element.
    4. Checks how you propose to handle the release.
    5. Retrieves data from the drag element asynchronously.
    6. Creates a new geocache with a name based on incoming string data.
    7. Adds the new geocache to the data source.
    8. Sets the new item in the collection view. You invoke this on the main thread since the data retrieval completion block is run in an internal queue.

    Responding to Drops

    Add the following to the end of UICollectionViewDropDelegate extension:

      func collectionView (
    _ collectionView: UICollectionView,
    dropSessionDidUpdate session: UIDropSession,
    withDestinationIndexPath destinationIndexPath: IndexPath?
    ) -> UICollectionViewDropProposal {
    if session.localDragSession! = null {
    return UICollectionViewDropProposal (operation: prohibited)
    } else {
    return UICollectionViewDropProposal (
    operation: .copy,
    Purpose: .insertAtDestinationIndexPath)
    }
    }
    

    You specify the response to an item being dragged. This includes providing visual feedback to the user.

    The code prohibits drag and drop in the app. It suggests copy operations for items dropped from another app.

    Add the following to viewDidLoad () after assigning the drag delegate:

      collectionView.dropDelegate = self
    

    This sets the view controller as a drop delegate.

    Build and run the app. With Reminders in shared view, you must confirm that you can drag a reminder to the currently running collection:

    If you try to drop into the middle of the list, you will I see that it just adds to the end of the list. You will improve this later.

    Try dragging and dropping a geocache inside the app. Confirm that you are receiving a visual signal that this is not allowed:

    It is not ideal, so you want to work on the next one.

    Drag and drop in the same app

    in CachesViewController.swift go to collectionView (_: dropSessionDidUpdate: withDestinationIndexPath :) and replace the prohibited return statement with this code: 19659019] guard session.items.count == 1 else {
    return UICollectionViewDropProposal (operation: Cancel)
    }

    if collectionView.hasActiveDrag {
    return UICollectionViewDropProposal (operation: .move,
    Purpose: .insertAtDestinationIndexPath)
    } else {
    return UICollectionViewDropProposal (operation: .copy,
    Purpose: .insertAtDestinationIndexPath)
    }

    If more than one item is selected, the code cancels the release. For the one-drop item, suggest a move if you are in the same collection view. Otherwise, suggest a copy.

    In CachesDataSource.swift add the following method to the extension:

      func moveGeocache (at sourceIndex: Int, to destinationIndex: Int) {
    guard sourceIndex! = destinationIndex else {return}
    
    la geocache = geocache [sourceIndex]
    geocaches.remove (at: sourceIndex)
    geocaches.insert (geocache, at: destinationIndex)
    }
    

    This repositiones a geocache in the data source.

    Return to CachesViewController.swift and in collectionView (_: performDropWith :) replace destinationIndexPath task with the following:

      add destinationIndexPath: IndexPath:
    if let indexPath = coordinator.destinationIndexPath {
    destinationIndexPath = indexPath
    } else {
    destinationIndexPath = IndexPath (
    element: collectionView.numberOfItems (section: 0),
    section: 0)
    }
    

    Here you are looking for an index path that specifies where to insert the element. If none is found, insert the item at the end of the collection view.

    Add the following just before the .copy case:

      case. Move:
    print ("Moving ...")
    // 1
    if let sourceIndexPath = item.sourceIndexPath {
    // 2
    collectionView.performBatchUpdates ({
    dataSource.moveGeocache (
    at: sourceIndexPath.item,
    to: destinationIndexPath.item)
    collectionView.deleteItems (kl: [sourceIndexPath])
    collectionView.insertItems (kl: [destinationIndexPath])
    })
    // 3
    coordinator.drop (item.dragItem, toItemAt: destinationIndexPath)
    }
    

    This code block:

    1. Get the source index path that you should have access to drag and drop in the same collection view.
    2. Performs batch updates to move geocache in the data source and collection view.
    3. Animates insertion of the ripped geocache into the collection view.

    Follow My Moves

    Build and run the app. Make sure that dragging and dropping a geocache over collection views creates a copy and logs the copy message:

    Test that you can also move a geocache in the same collection view and see the relocation message logged: [19659002] [19659002] You may have noticed some inefficiency when dragging and dropping over the collection views. You work in the same app, but you make a copy of the object with low faith. Not to mention, you make a copy!

    Sure, you can do better.

    Drop Experience Optimization

    You can do a few optimizations to improve your drop implementation and experience.

    Using In-Memory Data

    You should take advantage of your access to full geocache structure in the same app.

    Go to CachesDataSource.swift . Add the following to dragItems (for :) just before the return statement:

      dragItem.localObject = geocache
    

    You assign geocache to the drag element property. This enables faster recovery of goods later.

    Go to CachesViewController.swift . In collectionView (_: performDropWith :) replace the code inside the .copy case with the following:

      if let geocache = item.dragItem.localObject as? Geocache {
    print ("Copy from the same app ...")
    dataSource.addGeocache (geocache, at: destinationIndexPath.item)
    DispatchQueue.main.async {
    collectionView.insertItems (kl: [destinationIndexPath])
    }
    } else {
    print ("Copy from different app ...")
    la itemProvider = item.dragItem.itemProvider
    itemProvider.loadObject (ofClass: NSString.self) {string, error in
    if let string = string like? String {
    la geocache = Geocache (
    name: string, summary: "Unknown", latitude: 0.0, longitude: 0.0)
    dataSource.addGeocache (geocache, at: destinationIndexPath.item)
    DispatchQueue.main.async {
    collectionView.insertItems (kl: [destinationIndexPath])
    }
    }
    }
    }
    

    Here, the code that handles items dropped from another app has not changed. For items copied from the same app, you get the stored geocache from localObject and use it to create a new geocache.

    Build and run the app. Confirm that dragging and dropping collection views now recreates the geocache structure:

    Moving Items Over Collection Views

    You now have a better representation of the geocache. It's great, but you should really move the geocache over collection views instead of copying it.

    Continued in CachesViewController.swift replace collectionView (_: dropSessionDidUpdate: withDestinationIndexPath :) implementation with the following:

      watch session.localDragSession! = zero else {
    return UICollectionViewDropProposal (
    operation: .copy,
    Purpose: .insertAtDestinationIndexPath)
    }
    guard session.items.count == 1 else {
    return UICollectionViewDropProposal (operation: Cancel)
    }
    return UICollectionViewDropProposal (
    operation :. move,
    Purpose: .insertAtDestinationIndexPath)
    

    You now manage drops within the same app as move operations.

    Go to File ▸ New ▸ File… and select iOS ▸ Source ▸ Swift File template. Click Next . Name the file CacheDragCoordinator.swift and click Create .

    Add the following to the end of the file:

      class CacheDragCoordinator {
    la sourceIndexPath: IndexPath
    var dragCompleted = false
    var isReordering = false
    
    init (sourceIndexPath: IndexPath) {
    self.sourceIndexPath = sourceIndexPath
    }
    }
    

    You have created a class to coordinate drag and drop in the same app, where you set up tracking properties:

    • Where to start.
    • When completed.
    • If the collection view is to be relocated after the release.

    Switch to CachesDataSource.swift and add the following method to the extension:

      func deleteGeocache (by index: Int) {
    geocaches.remove (at: index)
    }
    

    This method removes a geocache at the specified index. You use this help method when you reorder collection view items.

    Go to CachesViewController.swift . Add the following to the collectionView (_: itemsForBeginning: at) just before the return statement:

      light dragCoordinator = CacheDragCoordinator (sourceIndexPath: indexPath)
    session.localContext = dragCoordinator
    

    Here you initialize a drag coordinator with the start index path. You then add this object to the operating session property that stores custom data. This data is only visible to apps where drag activity starts.

    Are You My App?

    Finn collectionView (_: performDropWith :) . Replace the code in the .copy case with the following:

      print ("Copy from another app ...")
    la itemProvider = item.dragItem.itemProvider
    itemProvider.loadObject (ofClass: NSString.self) {string, error in
    if let string = string like? String {
    la geocache = Geocache (
    name: string, summary: "Unknown", latitude: 0.0, longitude: 0.0)
    dataSource.addGeocache (geocache, at: destinationIndexPath.item)
    DispatchQueue.main.async {
    collectionView.insertItems (kl: [destinationIndexPath])
    }
    }
    }
    

    You have simplified the copy path to only handle drops from another app.

    Replace the code in . Move the case with the following:

      // 1
    guard la dragCoordinator =
    coordinator.session.localDragSession? .localContext as? CacheDragCoordinator
    else {return}
    // 2
    if let sourceIndexPath = item.sourceIndexPath {
    print ("Moving in the same assembly view ...")
    // 3
    dragCoordinator.is Reorder = true
    // 4
    collectionView.performBatchUpdates ({
    dataSource.moveGeocache (at: sourceIndexPath.item, to: destinationIndexPath.item)
    collectionView.deleteItems (kl: [sourceIndexPath])
    collectionView.insertItems (kl: [destinationIndexPath])
    })
    } else {
    print ("Moving Between Collection Views ...")
    // 5
    dragCoordinator.is Reorder = false
    // 6
    if let geocache = item.dragItem.localObject as? Geocache {
    collectionView.performBatchUpdates ({
    dataSource.addGeocache (geocache, at: destinationIndexPath.item)
    collectionView.insertItems (kl: [destinationIndexPath])
    })
    }
    }
    // 7
    dragCoordinator.dragCompleted = true
    // 8
    coordinator.drop (item.dragItem, toItemAt: destinationIndexPath)
    

    Here's a step-by-step overview of what's happening:

    1. Get the drag coordinator.
    2. Check if the source index path for the drag element is specified. This means drag and drop is in the same assembly view.
    3. Inform the dragon coordinator that the collection view will be reorganized.
    4. Perform batch updates to move geocache in the data source and collection view. [19659041] Note that the aggregate view will not be reorganized.
    5. Download the locally stored geocache. Add it to the data source and insert it into the collection view.
    6. Let the dragon coordinator know that drag is complete.
    7. Animate the insertion of the dragged geocache in the collection view.

    Add the following method to your UICollectionViewDragDelegate extension:

      func collectionView (_ collectionView: UICollectionView,
    dragSessionDidEnd session: DRAFT Session) {
    // 1
    guard
    la dragCoordinator = session.localContext as? CacheDragCoordinator,
    dragCoordinator.dragCompleted == true,
    dragCoordinator.isReordering == false
    otherwise {
    return
    }
    // 2
    la dataSource = dataSourceForCollectionView (collectionView)
    let sourceIndexPath = dragCoordinator.sourceIndexPath
    // 3
    collectionView.performBatchUpdates ({
    dataSource.deleteGeocache (at: sourceIndexPath.item)
    collectionView.deleteItems (kl: [sourceIndexPath])
    })
    }
    

    This method is called when either drag is interrupted or the element is dropped. Here's how the code works:

    1. Check the dragon coordinator. If the release is complete and the collection view is not reorganized, it will continue.
    2. Get the data source and source index path to prepare for the updates.
    3. Perform batch updates to delete geocache from the data source and collection view. Remember that you previously added the same geocache to the drop destination. This takes care of removing it from the kite source.

    Build and run the app. Make sure that moving over the collection views actually moves the element and prints Moving between the collection views ... in the console:

    Adding a Placeholder

    It may take time to retrieve items from an external app and load them into the destination app. It is good practice to provide visual feedback to the user, such as displaying a placeholder.

    Replace .copy case in collectionView (_: performDropWith :) with the following: [19659020] print ("Copying from different app …")
    // 1
    let placeholder = UICollectionViewDropPlaceholder (
    insertionIndexPath: destinationIndexPath, reuseIdentifier: "CacheCell")
    // 2
    placeholder.cellUpdateHandler = {cell in
    if let cell = cell like? CacheCell {
    cell.cacheNameLabel.text = "Loading …"
    cell.cacheSummaryLabel.text = ""
    cell.cacheImageView.image = null
    }
    }
    // 3
    let context = coordinator.drop (item.dragItem, to: placeholder)
    la itemProvider = item.dragItem.itemProvider
    itemProvider.loadObject (ofClass: NSString.self) {string, error in
    if let string = string like? String {
    la geocache = Geocache (
    name: string, summary: "Unknown", latitude: 0.0, longitude: 0.0)
    // 4
    DispatchQueue.main.async {
    context.commitInsertion {dataSourceUpdates: {_ in
    dataSource.addGeocache (geocache, at: destinationIndexPath.item)
    })
    }
    }
    }

    This is what happens:

    1. Create a placeholder cell for the new content.
    2. Define the block that configures the placeholder cell.
    3. Place the placeholder in the collection view.
    4. Commit the insertion to exchange the placeholder with the final cell.

    Build and run the app. Drag and drop an item from Reminders . Notice the brief appearance of the placeholder text when dropping the item in a collection view:

    Multiple Data Representations

    You can configure the data types you can deliver to a destination app or consume from a source app.

    When you create an item supplier using init (object :) the object you are passing must conform to NSItemProviderWriting . Adoption of the protocol includes specifying uniform type identifiers (UTIs) for the data you can export and handle the export for each data representation.

    For example, you may want to export a string representation of your geocache for apps that only include strings. Or you may want to export a photo representation for photo apps. For apps under your control that use geocaches, you may want to export the full data model.

    To properly consume dropped elements and turn them into geocaches, the data model should apply NSItemProviderReading . Then implement protocol methods to specify which data representations you can consume. You will also implement them to specify how you want to force incoming data based on what the source app sends.

    So far, you've been working on strings when dragging and dropping geocaches between apps. NSString automatically supports NSItemProviderWriting and NSItemProviderReading so you did not need to write any special code.

    To handle multiple data types, change the geocache data model. You can find this in the Geocache project, which is part of the Xcode workspace you have open.

    In the Geocache project, open Geocache.swift and add the following after Foundation import:

      import MobileCoreServices
    

    You need this framework to use predefined UTIs such as those representing PNGs.

    Add the following right after your last import:

      public let geocacheTypeId = "com.razeware.geocache"
    

    You create a custom string identifier to represent a geocache.

    Reading and Writing Geocaches

    Add the following extension to the end of the file:

      Extension Geocache: NSItemProviderWriting {
    // 1
    public static was writable TypeIdentifiersForItemProvider: [String] {
    return [geocacheTypeId,
                kUTTypePNG as String,
                kUTTypePlainText as String]
    }
    // 2
    public func loadData (
    withTypeIdentifier typeIdentifier: String,
    forItemProviderCompletionHandler completingHandler:
    @escaping (data?, wrong?) -> invalid)
    -> Progress? {
    if typeIdentifier == kUTTypePNG as string {
    // 3
    if let image = image {
    completionManager (image, zero)
    } else {
    completionManager (zero, zero)
    }
    } else if typeIdentifier == kUTTypePlainText as string {
    // 4
    completionHandler (name.data (user: .utf8), null)
    } else if typeIdentifier == geocacheTypeId {
    // 5
    do {
    la archive = NSKeyedArchiver (requiresSecureCoding: false)
    try archiver.encodeEncodable (self, forKey: NSKeyedArchiveRootObjectKey)
    archiver.finishEncoding ()
    la data = archiver.encodedData
    
    completionManager (data, null)
    } catch {
    completionManager (zero, zero)
    }
    }
    back zero
    }
    }
    

    Here you match NSItemProviderWriting and do the following:

    1. Specify data representations that you can deliver to the destination app. You will return a string ordered from the version of the highest fidelity to the object to the lowest.
    2. Implement the method of delivering data to the destination app when prompted. The system calls this when an item is released and passes in the correct type of identifier.
    3. Return the geocache image in the completion handler if a PNG identifier is submitted.
    4. Return the geocache name in the completion handler if a text identifier is provided.
    5. If the custom geocache type identifier is submitted, you return a data object corresponding to the entire geocache.

    Now add the following enum right after geocacheTypeId is assigned: [19659020] enum EncodingError: Error {
    case invalid
    }

    You will use this to return an error code when there are data loading issues.

    Then add the following to the end of the file:

      extension Geocache: NSItemProviderReading {
    // 1
    public static was readable TypeIdentifiersForItemProvider: [String] {
    return [geocacheTypeId,
                kUTTypePlainText as String]
    }
    // 2
    public static func object (withItemProviderData data: Data,
    typeIdentifier: String) throws -> Even {
    if typeIdentifier == kUTTypePlainText as string {
    // 3
    guard let name = String (data: data, encoding: .utf8) else {
    throw EncodingError.invalidData
    }
    return self.init (
    name: name,
    summary: "Unknown",
    latitude: 0.0,
    longitude: 0.0)
    } else if typeIdentifier == geocacheTypeId {
    // 4
    do {
    let unarchiver = try NSKeyedUnarchiver (forReadingFrom: data)
    guard la geocache =
    try unarchiver.decodeTopLevelDecodable (
    Geocache.self, forKey: NSKeyedArchiveRootObjectKey) else {
    throw EncodingError.invalidData
    }
    return self.init (geocache)
    } catch {
    throw EncodingError.invalidData
    }
    } else {
    throw EncodingError.invalidData
    }
    }
    }
    

    Here you match NSItemProviderReading to specify how to handle incoming data. Here's what happens:

    1. Specify the types of incoming data the model can consume. The UTIs listed here represent a geocache and text.
    2. Implement the required protocol method to import data given a type of identifier.
    3. For a text identifier, create a new geocache with the name based on the incoming text and placeholder information.
    4. For the geocache identifier, decode the incoming data and use it to create a full geocache model.

    Incorrect or unrecognized identifiers discard the error you defined before.

    Back to My app

    Change the active scheme to Geocache and build the project. Then change the active scheme back to CacheMaker .

    In CacheMaker go to CacheesDataSource.swift and inside dragItems (for :) change itemProvider the task to:

      let itemProvider = NSItemProvider (object: geocache)
    

    Here you can initialize your goods supplier with a geocache since the model adopted NSItemProviderWriting to export data correctly.

    Open CachesViewController.swift and find collectionView (_: performDropWith :) . In the case .copy replace the item supplier's loadObject conversation with the following:

      itemProvider.loadObject (ofClass: Geocache.self) {geocache, _ in
    if la geocache = geocache like? Geocache {
    DispatchQueue.main.async {
    context.commitInsertion {dataSourceUpdates: {_ in
    dataSource.addGeocache (geocache, at: destinationIndexPath.item)
    })
    }
    }
    }
    

    You have modified the drop handler to load objects of type Geocache . The completion block now returns a geocache that you can use directly.

    Build and run the app. Place Reminders in Split View if necessary. Check that dragging and dropping items between Reminders and CacheMaker works as before:

    Include Photos in Split View to replace reminders . Drag a geocache from the current path and drop it into Photos to confirm that you can export a geocache image representation:

    You can test the export of the data model path with a temporary hack. Go to CachesViewController.swift and in collectionView (_: dropSessionDidUpdate: withDestinationIndexPath :) replace the line that returns the move operation with the following:

      return UICollectionViewDropPropos
    operation: .copy,
    Purpose: .insertAtDestinationIndexPath)
    

    You configure drag and drop in the same app as copy operations. This triggers the code to export and import the full data model.

    Build and run the app. Test at å flytte et element i appen lager en riktig kopi av geocache:

    Gå tilbake til det midlertidige hacket i collectionView (_: dropSessionDidUpdate: withDestinationIndexPath:) slik at i-appen dra-og-slipp utfører en flytteoperasjon:

     return UICollectionViewDropProposal (
      drift:. flytte,
      hensikt: .insertAtDestinationIndexPath)
    

    Bygg og kjør appen for å komme tilbake til forhåndshackforhold.

    Legge til draktsupport til en tilpasset visning

    Du har sett hvordan du kan legge til dra og slipp-støtte til samlevisninger. Å legge denne støtten til tabellvisninger følger en lignende prosess.

    Du kan også legge til dra og slipp-funksjonalitet til tilpassede visninger. De grunnleggende trinnene involverer:

    • Legge til et interaksjonsobjekt i den tilpassede visningen.
    • Implementering av protokollmetodene i samhandlingsdelegatet for å skaffe eller konsumere data.

    Det er på tide å introdusere CacheEditor ledsager-appen din for redigering av geocacher. Change the active scheme to CacheEditor. Build and run the app then rotate the device to landscape mode:

    View CacheMaker in Split View, placing it to the left of CacheEditor. Resize the Split View so both apps take up about half the width:

    Try dragging a geocache from CacheEditor into CacheMaker. You’re in for a frustrating experience, my friend.

    You’ll be working with one key file in CacheEditorCacheDetailViewController.swiftwhich displays geocache details. Open that file and add the following code to the end:

    // MARK: - UIDragInteractionDelegate
    extension CacheDetailViewController: UIDragInteractionDelegate {
      func dragInteraction(
        _ interaction: UIDragInteraction, 
        itemsForBeginning session: UIDragSession) 
          -> [UIDragItem] {
        let itemProvider = NSItemProvider(object: geocache)
        let dragItem = UIDragItem(itemProvider: itemProvider)
        return [ dragItem ]
    }
    }
    

    Here, you adopt UIDragInteractionDelegate and implement the method that’s called when a drag activity starts. The code should look similar to what you’ve seen in CacheMaker. You return drag items with a geocache as the item provider.

    Add the following to viewDidLoad() right after the call to super:

    view.addInteraction(UIDragInteraction(delegate: self))  
    

    Here, you create a drag interaction with the view controller as the delegate. You then add the interaction to the view.

    Build and run CacheEditor. Verify that you can now drag a geocache from CacheEditor and drop it into CacheMaker:

    Try dragging a geocache from CacheMaker into CacheEditor. While the drag starts, it doesn’t drop. That’s your next mission.

    Adding Drop Support to a Custom View

    Still in CacheDetailViewController.swiftadd the following to the end of the file:

    // MARK: - UIDropInteractionDelegate
    extension CacheDetailViewController : UIDropInteractionDelegate {
      func dropInteraction(
        _ interaction: UIDropInteraction, 
        canHandle session: UIDropSession) 
          -> Bool {
        return session.canLoadObjects(ofClass: Geocache.self)
    }
    
      func dropInteraction(
        _ interaction: UIDropInteraction, 
        sessionDidUpdate session: UIDropSession) 
          -> UIDropProposal {
        return UIDropProposal(operation: .copy)
    }
    
      func dropInteraction(
          _ interaction: UIDropInteraction, 
          performDrop session: UIDropSession) {
        session.loadObjects(ofClass: Geocache.self) { items in
          if let geocaches = items as? [Geocache],
            let geocache = geocaches.first {
            self.geocache = geocache
            self.configureView()
    }
    }
    }
    }
    

    Here, you adopt UIDropInteractionDelegate and implement the optional methods to track and properly handle drops.

    The first method restricts drops to apps that pass in a Geocache object.

    The second method returns a copy operation as the proposed method to handle drops. This method is called when the user drags an item over the drop interaction’s view. Even though this protocol method is optional, you need to implement it to accept drops.

    The last protocol method is called when the drop gesture is completed. You grab the item provider from the session and start the data fetch. Then, you load in the first geocache and update the view.

    Next, add this code to viewDidLoad() after the drag interaction setup:

    view.addInteraction(UIDropInteraction(delegate: self))  
    

    With this, you create a drop interaction and set the view controller as the delegate. You then add the interaction to the view.

    Build and run the app. Verify that you can drop a geocache into CacheEditor:

    With very few lines of code, you’ve added drag and drop support to a custom view.

    Where to Go From Here?

    Congratulations! You’ve used drag and drop to make the geocache management sample apps functional.

    Use the Download Materials button at the top or bottom of this tutorial to download the final project.

    You should now be able to add drag and drop to supercharge many in-app and cross-app experiences. Check out the Drag and Drop in Table and Collection Views and Drag and Drop with Multiple Data Representations and Custom Views video tutorials for additional tips and to see how to add this functionality to table views.

    I hope you enjoyed this tutorial. If you have any comments or questions, please join the forum discussion below!


    Source link