С выпуском iOS-11 Apple представила перетаскивание в UITableView и UICollectionView со специализированным API, который работает на концепции взаимодействий ( что-то вроде жестов).

Представление таблицы и представление коллекции имеют похожие API для поддержки перетаскивания с некоторыми небольшими отличиями.

Перетаскивание поддерживается как в iPhone, так и в iPad. На iPad пользователь может перетаскивать содержимое нескольких приложений. Но для iPhone эта поддержка предоставляется только в одном приложении.

Мы обсудим, как перетаскивание работает в представлениях коллекций, и то же самое будет применимо к представлениям таблиц. Я отмечу различия в табличном представлении везде, где это необходимо. Так что не беспокойтесь об этом. Он будет работать как для представления коллекции, так и для представления таблицы.

Приступим 🚀

Применение перетаскивания в представлении коллекции - это многоэтапный процесс. Мы рассмотрим каждую деталь, необходимую для интеграции перетаскивания элементов в представление коллекции.

UICollectionViewDragDelegate

Чтобы включить перетаскивание, настраиваемый объект должен соответствовать протоколу UICollectionViewDragDelegate .

Он содержит несколько методов, которые можно реализовать для настройки поведения перетаскивания представления коллекции.

Единственный обязательный метод этого протокола, который вам нужно использовать для поддержки поведения перетаскивания:

collectionView(_:itemsForBeginning:at:)

Мы рассмотрим подробности метода в следующих разделах.

Кроме того, установите объект делегата dragDelegate представления коллекции в viewDidLoad(), чтобы управлять перетаскиванием элементов из него.

collectionView.dragDelegate = self

Протокол UICollectionViewDropDelegate

Чтобы разрешить отбрасывание, настраиваемый объект должен соответствовать протоколу UICollectionViewDropDelegate .

Единственный обязательный метод этого протокола:

collectionView(_:performDropWith:)

При необходимости вы можете реализовать другие методы, чтобы настроить поведение перетаскивания представления вашей коллекции.

Кроме того, назначьте свой настраиваемый объект делегата свойству dropDelegate представления коллекции в viewDidLoad().

collectionView.dropDelegate = self

Включение взаимодействия с перетаскиванием

Чтобы включить / отключить перетаскивание, вы можете настроить свойство dragInteractionEnabled представления вашей коллекции.

Значение этого свойства по умолчанию - true на iPad и false на iPhone.

Итак, установите для него значение true, чтобы разрешить перетаскивание содержимого из представления коллекции, если вы предоставляете поддержку перетаскивания в iPhone.

collectionView.dragInteractionEnabled = true

Перетаскивание одного объекта

Теперь, когда мы настроили наше представление коллекции для поддержки перетаскивания, пришло время написать код, который сделает это возможным.

Начнем с введения в некоторые важные классы, необходимые для поддержки перетаскивания. Вот так:

  1. NSItemProvider - Поставщик элементов для передачи данных или файла между процессами во время перетаскивания.
  2. UIDragItem - представление базового элемента данных, перетаскиваемого из одного места в другое.

Каждый элемент, который нужно перетащить, должен быть представлен как объект UIDragItem.

Чтобы разрешить перетаскивание элементов из представления коллекции, реализуйте единственный требуемый метод UICollectionViewDragDelegate и верните один или несколько объектов UIDragItem для элемента в указанном indexPath.

МОЙ БОГ ! 😯 Что это было? Слишком много, чтобы понять .. !!!

Не волнуйся. Мы рассмотрим каждый шаг в приведенном ниже коде:

Посмотрим, что написано в коде.

  1. Строка 1 - реализовать метод collectionView(_:itemsForBeginning:at:) из UICollectionViewDragDelegate.
  2. Строка 3 - Получите данные, соответствующие indexPath выбранному элементу. Используйте модель, которую вы используете, в качестве источника данных представления коллекции.
    Здесь я использовал массив String объектов в качестве источника данных для представления коллекции. Таким образом, элемент будет соответствовать значению String.
  3. Строка 4 - Создайте объект NSItemProvider, используя item, извлеченный в Строке 3. item преобразуется в NSString, потому что NSItemProvider принимает объект, а swift String является типом значения, а не типом объекта.
  4. Строка 5 - Создайте объект UIDragItem из itemProvider.
  5. Строка 6 - localObject - это настраиваемый объект, связанный с элементом перетаскивания. Он доступен только для приложения, которое инициирует перетаскивание.
    Это необязательно, но позволяет быстрее перетаскивать контент в одном приложении.
  6. Строка 7 - возвращает массив dragItem.

Если вы хотите игнорировать перетаскивание, верните пустой массив.

Перетаскивание нескольких элементов

Мы видели, как перетащить отдельный элемент из представления коллекции. Что, если я хочу перетащить несколько элементов за один раз?

Что ж, у нас есть метод UICollectionViewDragDelegate для этого.

collectionView(_:itemsForAddingTo:at:point:)

Этот метод добавляет указанные элементы в существующий сеанс перетаскивания.

Элементы добавляются в активный сеанс перетаскивания с помощью одного нажатия. Если вы не реализуете этот метод, касания в представлении коллекции запускают выбор элементов или другое поведение.

Его реализация аналогична реализации collectionView(_:itemsForBeginning:at:).

Вы можете добавить свои собственные ограничения в соответствии с вашими требованиями.

Пример: игнорируйте добавление элемента, если он уже существует в представлении коллекции, в котором вы его удаляете. В этом случае верните пустой массив.

Отбросить предложение - UICollectionViewDropProposal

Мы выбрали предмет и перетащили его. Теперь мы не можем просто перетаскивать элемент долго. Что с этим теперь делать? Куда закинуть? Что произойдет, когда я уроню предмет?

Все эти вопросы указывают на очень конкретный вопрос:
«Как вы собираетесь обрабатывать падение в указанном месте?
Вы хотите скопировать объект или просто переместить его на новое место?
Или вы хотите запретить движение в каких-то определенных условиях?
О нет .. !! Могу я просто отменить его? »

Так так так..!!! На все ваши вопросы есть один ответ - Отказаться от предложения. Предложение о перетаскивании, как следует из названия, - это предложение о том, как вы собираетесь обрабатывать перетаскивание в определенном месте, когда пользователь поднимает палец.

UIDropProposal - конфигурация поведения при перетаскивании, необходимая, если представление принимает действия перетаскивания.

Каждое предложение определяет операцию - enum UIDropOperation, которая определяет, как действие перетаскивания разрешается, когда пользователь отбрасывает элемент перетаскивания. Операция определяется по типу:

  1. cancel - не передавать данные, отмена перетаскивания.
  2. forbidden - хотя операция перемещения или копирования обычно является допустимой в этом сценарии, операция удаления не разрешена.
  3. copy - данные, представленные элементами перетаскивания, должны быть скопированы в целевое представление.
  4. move - данные, представленные элементами перетаскивания, следует перемещать, а не копировать.

UICollectionViewDropProposal - подкласс UIDropProposal, посвященный представлениям коллекции для обработки предложений перетаскивания.

Предложение представления коллекции также определяет необязательное намерение - перечисление UICollectionViewDropIntent, которое определяет, как включить контент в представление коллекции. Вы можете вставить содержимое между элементами или добавить его к существующему элементу. Возможные значения намерения:

  1. unspecified - Не указано ни одного предложения по удалению.
  2. insertAtDestinationIndexPath - Вставить отброшенные элементы по указанному пути индекса.
  3. insertIntoDestinationIndexPath - включить отброшенные элементы в элемент по указанному пути индекса.

В представлении коллекции используется intent информация, чтобы предоставить пользователю соответствующую визуальную обратную связь.

Теперь, когда мы знаем, что такое предложение drop, нам нужно выяснить, как и где его реализовать.

UICollectionViewDropDelegate предоставляет метод, с помощью которого вы можете указать предложение перетаскивания, которое хотите использовать, а именно:

collectionView(_:dropSessionDidUpdate:withDestinationIndexPath:)

Пока пользователь перетаскивает контент, представление коллекции вызывает этот метод несколько раз, чтобы определить, как вы будете обрабатывать перетаскивание, если оно произойдет в указанном месте.

Поскольку этот метод вызывается неоднократно, пока пользователь перетаскивает представление коллекции, ваша реализация должна вернуться как можно быстрее.

В приведенном выше коде я использовал 2 свойства:

  1. LocalDragSession - локальный сеанс перетаскивания nil, если перетаскивание началось в другом приложении.
  2. HasActiveDrag - логическое значение, указывающее, были ли элементы выведены из представления коллекции и еще не удалены.

Вот что говорит приведенный выше код:

  1. Строка 16. Если перетаскивание началось в другом приложении, запретите перетаскивание.
  2. Строка 7. Иначе, если элемент был извлечен из того же представления коллекции, в котором вы его отбрасываете, то переместите (переупорядочите) его из исходного пути в целевой индекс.
  3. Строка 11. В противном случае, если вы отбрасываете его в другом представлении коллекции, тогда скопируйте элемент по пути индекса назначения.

Обработка падения - Копирование

Разобравшись с тем, как мы собираемся справиться с падением, давайте рассмотрим детали реализации того, что нам нужно сделать после того, как действительно произойдет сброс.

Чтобы разрешить удаление элементов в вашем представлении коллекции, реализуйте единственный требуемый метод UICollectionViewDropDelegate.

То есть:
collectionView(_:performDropWith:) - Сообщает вашему делегату включить данные перетаскивания в представление коллекции.

Этот метод предоставляет вам объект UICollectionViewDropCoordinator, который вы можете использовать для обработки перетаскивания. Используя coordinator, вы можете получить следующие элементы, чтобы обновить источник данных вашего представления коллекции:

  1. items - Перетаскиваемые элементы
  2. destinationIndexPath - Путь индекса, по которому нужно вставить элемент в представление коллекции. Это необязательное значение. nil возвращается, если элемент вставлен в пустое представление коллекции или в конец представления коллекции.
  3. proposal - Текущее предложение по включению выпавших предметов

Кроме того, при включении элементов используйте методы drop(_:to:) или drop(_:toItemAt:) объекта coordinator для анимации перехода от предварительного просмотра элемента перетаскивания к соответствующему элементу в представлении вашей коллекции.

Давайте теперь посмотрим на код.

Приведенный выше код довольно прост:

  1. destinationIndexPath извлекается из coordinator. Если это nil, последний путь индекса представления коллекции используется как место назначения для удаления элемента.
  2. Примите соответствующие меры, чтобы обновить представление коллекции на основе operation - move/copy/forbidden/cancel предложения перетаскивания.

Если ваше предложение сброса - cancel/forbidden, collectionView(_:performDropWith:) не будет вызываться для обработки сброса, и, следовательно, вам не нужно делать с этим ничего особенного.

Если предлагаемая обработка move, нам нужно изменить порядок элементов. Подробнее о переупорядочивании мы поговорим в следующем разделе.

В этом разделе давайте посмотрим, как справиться с выпадением, если мы собираемся copy элементы.

Вы можете использовать performBatchUpdates(_:completion:) представления коллекции, чтобы вносить изменения в представление коллекции.

Вы можете использовать этот метод в тех случаях, когда вы хотите внести несколько изменений в представление коллекции в одной анимированной операции, а не в нескольких отдельных анимациях.

Удаление обрабатывается перед вставкой в ​​пакетных операциях. Это означает, что индексы для удалений обрабатываются относительно индексов состояния представления коллекции перед пакетной операцией, а индексы для вставок обрабатываются относительно индексов состояния после всех удалений в пакетной операции.

Вот как это сделать:

Чтобы получить данные, соответствующие перетаскиваемому элементу, вы можете использовать один из следующих вариантов:

  1. localObject свойство элемента перетаскивания можно использовать, если оно установлено. Он будет доступен, если контент взят из другого места в вашем приложении.
  2. Кроме того, вы можете использовать свойство itemProvider элемента перетаскивания для извлечения данных.

Обработка выпадения - изменение порядка

Переупорядочивание ячеек происходит, когда предложение перетаскивания указано как - переместить.

Переупорядочение - перемещение элемента из пути исходного индекса в путь индекса назначения в одном / другом представлении коллекции или представлении таблицы в соответствии с вашими требованиями.

Здесь я прямо говорю о табличном представлении, потому что табличное представление и представление коллекции обрабатывают переупорядочение немного по-разному. Мы обсудим их обоих здесь.

В табличных представлениях функция переупорядочивания уже давно доступна. И хорошо, что мы можем продолжать использовать это 😅. Не нужно делать ничего особенного для поддержки переупорядочения в табличных представлениях. Вам просто необходимо:

  1. Верните move в качестве предложения по размещению в tableView(_:dropSessionDidUpdate:withDestinationIndexPath:) для поддержки изменения порядка.
  2. Реализовать методы tableView(_:canMoveRowAt:) и tableView(_:moveRowAt:to:) для UITableViewDataSource.

tableView(_:performDropWith:) не будет вызываться для обработки отбрасывания, если реализованы вышеуказанные UITableViewDataSource методы.

В представлениях коллекции переупорядочение выполняется так же, как и при копировании. Единственное отличие состоит в том, что при копировании вы вставляли новый элемент в путь индекса назначения, но при изменении порядка вам необходимо удалить элемент из пути исходного индекса и вставить его. по пути индекса назначения.

Немного изменена логика, в остальном все осталось прежним.

Примечание. destinationIndexPath, полученный из coordinator в collectionView(_:performDropWith:), может быть количеством элементов в представлении коллекции, если вы попытаетесь переупорядочить элемент до конца представления коллекции. В случае изменения порядка количество элементов в представлении коллекции останется прежним. Поэтому вам нужно обработать это явно, поскольку это может вызвать Array index out of bounds ошибку времени выполнения.

Ой, погоди! У меня destinationIndexPath, а где взять sourceIndexPath?

Что ж, у нас тоже есть ответ.

sourceIndexPath можно получить в соответствии с каждым элементом, полученным с помощью coordinator. Если элемент происходит из того же представления коллекции, это свойство содержит исходный путь индекса элемента.

В приведенном выше коде переупорядочивается один элемент. Вы можете сделать это для нескольких предметов в соответствии с вашими требованиями.

Скорость переупорядочивания - reorderingCadence

В случае переупорядочения, когда вы перетаскиваете элемент через представление коллекции, которое принимает перетаскивание, представление коллекции обеспечивает соответствующую визуальную обратную связь для пользователя - смещение / изменение порядка ячеек для размещения новой.

Мы можем контролировать скорость переупорядочения для лучшего взаимодействия с пользователем, используя reorderingCadence типа enum UICollectionViewReorderingCadence в объекте представления коллекции.

reorderingCadence может иметь следующие возможные значения:

  1. immediate - Элементы сразу же переупорядочиваются. (значение по умолчанию)
  2. fast - Пункты переупорядочиваются быстро, но с небольшой задержкой.
  3. slow - Пункты переупорядочиваются после задержки.

Установите соответствующее значение reorderingCadence для объекта представления коллекции в viewDidLoad().

collectionView.reorderingCadence = .fast

Перетащите предварительный просмотр - UIDragPreviewParameters

Когда элемент поднимается, представление коллекции использует видимые границы элемента для создания предварительного просмотра по умолчанию. Если вы хотите настроить внешний вид элемента, вы можете сделать это с помощью параметров предварительного просмотра.

UIDragPreviewParameters - набор параметров для настройки внешнего вида предварительного просмотра элемента перетаскивания.

Параметры определяют различные визуальные аспекты предварительного просмотра, включая цвет фона и видимую область представления, связанную с предварительным просмотром.

Если вы не хотите вносить какие-либо изменения в предварительный просмотр перетаскивания, не применяйте этот метод и не возвращайте nil , если вы его реализуете.

Чтобы настроить предварительный просмотр перетаскивания, реализуйте метод collectionView(_:dragPreviewParametersForItemAt:) для UICollectionViewDropDelegate.

Вот и все. Это все, что вам нужно, чтобы функция drop-drop работала в виде коллекции и таблицы. Давай ... попробуй !!

Образец проекта

Вы можете скачать образец проекта здесь.

промо акции

Не забудьте прочитать другие мои статьи:

  1. Все о Codable в Swift 4
  2. Все, что вы всегда хотели знать об уведомлениях в iOS
  3. Раскрась с ГРАДИЕНТАМИ - iOS
  4. Все, что вам нужно знать о расширениях Today Extensions (Widget) в iOS 10
  5. Выбор UICollectionViewCell стал проще… !!

Не стесняйтесь оставлять комментарии, если у вас есть сомнения.