С выпуском 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
Перетаскивание одного объекта
Теперь, когда мы настроили наше представление коллекции для поддержки перетаскивания, пришло время написать код, который сделает это возможным.
Начнем с введения в некоторые важные классы, необходимые для поддержки перетаскивания. Вот так:
- NSItemProvider - Поставщик элементов для передачи данных или файла между процессами во время перетаскивания.
- UIDragItem - представление базового элемента данных, перетаскиваемого из одного места в другое.
Каждый элемент, который нужно перетащить, должен быть представлен как объект UIDragItem
.
Чтобы разрешить перетаскивание элементов из представления коллекции, реализуйте единственный требуемый метод UICollectionViewDragDelegate
и верните один или несколько объектов UIDragItem
для элемента в указанном indexPath
.
МОЙ БОГ ! 😯 Что это было? Слишком много, чтобы понять .. !!!
Не волнуйся. Мы рассмотрим каждый шаг в приведенном ниже коде:
Посмотрим, что написано в коде.
- Строка 1 - реализовать метод
collectionView(_:itemsForBeginning:at:)
изUICollectionViewDragDelegate
. - Строка 3 - Получите данные, соответствующие
indexPath
выбранному элементу. Используйте модель, которую вы используете, в качестве источника данных представления коллекции.
Здесь я использовал массивString
объектов в качестве источника данных для представления коллекции. Таким образом, элемент будет соответствовать значениюString
. - Строка 4 - Создайте объект
NSItemProvider
, используяitem
, извлеченный в Строке 3.item
преобразуется вNSString
, потому чтоNSItemProvider
принимает объект, а swiftString
является типом значения, а не типом объекта. - Строка 5 - Создайте объект
UIDragItem
изitemProvider
. - Строка 6 -
localObject
- это настраиваемый объект, связанный с элементом перетаскивания. Он доступен только для приложения, которое инициирует перетаскивание.
Это необязательно, но позволяет быстрее перетаскивать контент в одном приложении. - Строка 7 - возвращает массив
dragItem
.
Если вы хотите игнорировать перетаскивание, верните пустой массив.
Перетаскивание нескольких элементов
Мы видели, как перетащить отдельный элемент из представления коллекции. Что, если я хочу перетащить несколько элементов за один раз?
Что ж, у нас есть метод UICollectionViewDragDelegate
для этого.
collectionView(_:itemsForAddingTo:at:point:)
Этот метод добавляет указанные элементы в существующий сеанс перетаскивания.
Элементы добавляются в активный сеанс перетаскивания с помощью одного нажатия. Если вы не реализуете этот метод, касания в представлении коллекции запускают выбор элементов или другое поведение.
Его реализация аналогична реализации collectionView(_:itemsForBeginning:at:)
.
Вы можете добавить свои собственные ограничения в соответствии с вашими требованиями.
Пример: игнорируйте добавление элемента, если он уже существует в представлении коллекции, в котором вы его удаляете. В этом случае верните пустой массив.
Отбросить предложение - UICollectionViewDropProposal
Мы выбрали предмет и перетащили его. Теперь мы не можем просто перетаскивать элемент долго. Что с этим теперь делать? Куда закинуть? Что произойдет, когда я уроню предмет?
Все эти вопросы указывают на очень конкретный вопрос:
«Как вы собираетесь обрабатывать падение в указанном месте?
Вы хотите скопировать объект или просто переместить его на новое место?
Или вы хотите запретить движение в каких-то определенных условиях?
О нет .. !! Могу я просто отменить его? »
Так так так..!!! На все ваши вопросы есть один ответ - Отказаться от предложения. Предложение о перетаскивании, как следует из названия, - это предложение о том, как вы собираетесь обрабатывать перетаскивание в определенном месте, когда пользователь поднимает палец.
UIDropProposal - конфигурация поведения при перетаскивании, необходимая, если представление принимает действия перетаскивания.
Каждое предложение определяет операцию - enum UIDropOperation, которая определяет, как действие перетаскивания разрешается, когда пользователь отбрасывает элемент перетаскивания. Операция определяется по типу:
cancel
- не передавать данные, отмена перетаскивания.forbidden
- хотя операция перемещения или копирования обычно является допустимой в этом сценарии, операция удаления не разрешена.copy
- данные, представленные элементами перетаскивания, должны быть скопированы в целевое представление.move
- данные, представленные элементами перетаскивания, следует перемещать, а не копировать.
UICollectionViewDropProposal - подкласс UIDropProposal, посвященный представлениям коллекции для обработки предложений перетаскивания.
Предложение представления коллекции также определяет необязательное намерение - перечисление UICollectionViewDropIntent, которое определяет, как включить контент в представление коллекции. Вы можете вставить содержимое между элементами или добавить его к существующему элементу. Возможные значения намерения:
unspecified
- Не указано ни одного предложения по удалению.insertAtDestinationIndexPath
- Вставить отброшенные элементы по указанному пути индекса.insertIntoDestinationIndexPath
- включить отброшенные элементы в элемент по указанному пути индекса.
В представлении коллекции используется intent
информация, чтобы предоставить пользователю соответствующую визуальную обратную связь.
Теперь, когда мы знаем, что такое предложение drop, нам нужно выяснить, как и где его реализовать.
UICollectionViewDropDelegate
предоставляет метод, с помощью которого вы можете указать предложение перетаскивания, которое хотите использовать, а именно:
collectionView(_:dropSessionDidUpdate:withDestinationIndexPath:)
Пока пользователь перетаскивает контент, представление коллекции вызывает этот метод несколько раз, чтобы определить, как вы будете обрабатывать перетаскивание, если оно произойдет в указанном месте.
Поскольку этот метод вызывается неоднократно, пока пользователь перетаскивает представление коллекции, ваша реализация должна вернуться как можно быстрее.
В приведенном выше коде я использовал 2 свойства:
- LocalDragSession - локальный сеанс перетаскивания
nil
, если перетаскивание началось в другом приложении. - HasActiveDrag - логическое значение, указывающее, были ли элементы выведены из представления коллекции и еще не удалены.
Вот что говорит приведенный выше код:
- Строка 16. Если перетаскивание началось в другом приложении, запретите перетаскивание.
- Строка 7. Иначе, если элемент был извлечен из того же представления коллекции, в котором вы его отбрасываете, то переместите (переупорядочите) его из исходного пути в целевой индекс.
- Строка 11. В противном случае, если вы отбрасываете его в другом представлении коллекции, тогда скопируйте элемент по пути индекса назначения.
Обработка падения - Копирование
Разобравшись с тем, как мы собираемся справиться с падением, давайте рассмотрим детали реализации того, что нам нужно сделать после того, как действительно произойдет сброс.
Чтобы разрешить удаление элементов в вашем представлении коллекции, реализуйте единственный требуемый метод UICollectionViewDropDelegate
.
То есть: collectionView(_:performDropWith:)
- Сообщает вашему делегату включить данные перетаскивания в представление коллекции.
Этот метод предоставляет вам объект UICollectionViewDropCoordinator, который вы можете использовать для обработки перетаскивания. Используя coordinator
, вы можете получить следующие элементы, чтобы обновить источник данных вашего представления коллекции:
items
- Перетаскиваемые элементыdestinationIndexPath
- Путь индекса, по которому нужно вставить элемент в представление коллекции. Это необязательное значение.nil
возвращается, если элемент вставлен в пустое представление коллекции или в конец представления коллекции.proposal
- Текущее предложение по включению выпавших предметов
Кроме того, при включении элементов используйте методы drop(_:to:)
или drop(_:toItemAt:)
объекта coordinator
для анимации перехода от предварительного просмотра элемента перетаскивания к соответствующему элементу в представлении вашей коллекции.
Давайте теперь посмотрим на код.
Приведенный выше код довольно прост:
destinationIndexPath
извлекается изcoordinator
. Если этоnil
, последний путь индекса представления коллекции используется как место назначения для удаления элемента.- Примите соответствующие меры, чтобы обновить представление коллекции на основе
operation
-move/copy/forbidden/cancel
предложения перетаскивания.
Если ваше предложение сброса - cancel/forbidden
, collectionView(_:performDropWith:)
не будет вызываться для обработки сброса, и, следовательно, вам не нужно делать с этим ничего особенного.
Если предлагаемая обработка move
, нам нужно изменить порядок элементов. Подробнее о переупорядочивании мы поговорим в следующем разделе.
В этом разделе давайте посмотрим, как справиться с выпадением, если мы собираемся copy
элементы.
Вы можете использовать performBatchUpdates(_:completion:)
представления коллекции, чтобы вносить изменения в представление коллекции.
Вы можете использовать этот метод в тех случаях, когда вы хотите внести несколько изменений в представление коллекции в одной анимированной операции, а не в нескольких отдельных анимациях.
Удаление обрабатывается перед вставкой в пакетных операциях. Это означает, что индексы для удалений обрабатываются относительно индексов состояния представления коллекции перед пакетной операцией, а индексы для вставок обрабатываются относительно индексов состояния после всех удалений в пакетной операции.
Вот как это сделать:
Чтобы получить данные, соответствующие перетаскиваемому элементу, вы можете использовать один из следующих вариантов:
localObject
свойство элемента перетаскивания можно использовать, если оно установлено. Он будет доступен, если контент взят из другого места в вашем приложении.- Кроме того, вы можете использовать свойство
itemProvider
элемента перетаскивания для извлечения данных.
Обработка выпадения - изменение порядка
Переупорядочивание ячеек происходит, когда предложение перетаскивания указано как - переместить.
Переупорядочение - перемещение элемента из пути исходного индекса в путь индекса назначения в одном / другом представлении коллекции или представлении таблицы в соответствии с вашими требованиями.
Здесь я прямо говорю о табличном представлении, потому что табличное представление и представление коллекции обрабатывают переупорядочение немного по-разному. Мы обсудим их обоих здесь.
В табличных представлениях функция переупорядочивания уже давно доступна. И хорошо, что мы можем продолжать использовать это 😅. Не нужно делать ничего особенного для поддержки переупорядочения в табличных представлениях. Вам просто необходимо:
- Верните
move
в качестве предложения по размещению вtableView(_:dropSessionDidUpdate:withDestinationIndexPath:)
для поддержки изменения порядка. - Реализовать методы
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
может иметь следующие возможные значения:
immediate
- Элементы сразу же переупорядочиваются. (значение по умолчанию)fast
- Пункты переупорядочиваются быстро, но с небольшой задержкой.slow
- Пункты переупорядочиваются после задержки.
Установите соответствующее значение reorderingCadence
для объекта представления коллекции в viewDidLoad()
.
collectionView.reorderingCadence = .fast
Перетащите предварительный просмотр - UIDragPreviewParameters
Когда элемент поднимается, представление коллекции использует видимые границы элемента для создания предварительного просмотра по умолчанию. Если вы хотите настроить внешний вид элемента, вы можете сделать это с помощью параметров предварительного просмотра.
UIDragPreviewParameters - набор параметров для настройки внешнего вида предварительного просмотра элемента перетаскивания.
Параметры определяют различные визуальные аспекты предварительного просмотра, включая цвет фона и видимую область представления, связанную с предварительным просмотром.
Если вы не хотите вносить какие-либо изменения в предварительный просмотр перетаскивания, не применяйте этот метод и не возвращайте nil
, если вы его реализуете.
Чтобы настроить предварительный просмотр перетаскивания, реализуйте метод collectionView(_:dragPreviewParametersForItemAt:)
для UICollectionViewDropDelegate
.
Вот и все. Это все, что вам нужно, чтобы функция drop-drop работала в виде коллекции и таблицы. Давай ... попробуй !!
Образец проекта
Вы можете скачать образец проекта здесь.
промо акции
Не забудьте прочитать другие мои статьи:
- Все о Codable в Swift 4
- Все, что вы всегда хотели знать об уведомлениях в iOS
- Раскрась с ГРАДИЕНТАМИ - iOS
- Все, что вам нужно знать о расширениях Today Extensions (Widget) в iOS 10
- Выбор UICollectionViewCell стал проще… !!
Не стесняйтесь оставлять комментарии, если у вас есть сомнения.