版本記錄
版本號(hào) | 時(shí)間 |
---|---|
V1.0 | 2019.08.13 星期二 |
前言
iOS中有關(guān)視圖控件用戶能看到的都在UIKit框架里面,用戶交互也是通過UIKit進(jìn)行的糕韧。感興趣的參考上面幾篇文章咧叭。
1. UIKit框架(一) —— UIKit動(dòng)力學(xué)和移動(dòng)效果(一)
2. UIKit框架(二) —— UIKit動(dòng)力學(xué)和移動(dòng)效果(二)
3. UIKit框架(三) —— UICollectionViewCell的擴(kuò)張效果的實(shí)現(xiàn)(一)
4. UIKit框架(四) —— UICollectionViewCell的擴(kuò)張效果的實(shí)現(xiàn)(二)
5. UIKit框架(五) —— 自定義控件:可重復(fù)使用的滑塊(一)
6. UIKit框架(六) —— 自定義控件:可重復(fù)使用的滑塊(二)
7. UIKit框架(七) —— 動(dòng)態(tài)尺寸UITableViewCell的實(shí)現(xiàn)(一)
8. UIKit框架(八) —— 動(dòng)態(tài)尺寸UITableViewCell的實(shí)現(xiàn)(二)
9. UIKit框架(九) —— UICollectionView的數(shù)據(jù)異步預(yù)加載(一)
10. UIKit框架(十) —— UICollectionView的數(shù)據(jù)異步預(yù)加載(二)
11. UIKit框架(十一) —— UICollectionView的重用、選擇和重排序(一)
12. UIKit框架(十二) —— UICollectionView的重用持寄、選擇和重排序(二)
13. UIKit框架(十三) —— 如何創(chuàng)建自己的側(cè)滑式面板導(dǎo)航(一)
14. UIKit框架(十四) —— 如何創(chuàng)建自己的側(cè)滑式面板導(dǎo)航(二)
15. UIKit框架(十五) —— 基于自定義UICollectionViewLayout布局的簡單示例(一)
16. UIKit框架(十六) —— 基于自定義UICollectionViewLayout布局的簡單示例(二)
17. UIKit框架(十七) —— 基于自定義UICollectionViewLayout布局的簡單示例(三)
18. UIKit框架(十八) —— 基于CALayer屬性的一種3D邊欄動(dòng)畫的實(shí)現(xiàn)(一)
19. UIKit框架(十九) —— 基于CALayer屬性的一種3D邊欄動(dòng)畫的實(shí)現(xiàn)(二)
20. UIKit框架(二十) —— 基于UILabel跑馬燈類似效果的實(shí)現(xiàn)(一)
21. UIKit框架(二十一) —— UIStackView的使用(一)
22. UIKit框架(二十二) —— 基于UIPresentationController的自定義viewController的轉(zhuǎn)場和展示(一)
23. UIKit框架(二十三) —— 基于UIPresentationController的自定義viewController的轉(zhuǎn)場和展示(二)
開始
首先看下主要內(nèi)容
主要內(nèi)容:在這個(gè)
drag and drop
教程中源梭,您將構(gòu)建支持UICollectionViews
和兩個(gè)單獨(dú)的iOS應(yīng)用程序之間的drag and drop
娱俺。
下面看一下寫作環(huán)境
Swift 5, iOS 12, Xcode 10
Apple在iOS 11
中引入了拖放(drag and drop)
功能,允許用戶將項(xiàng)目從一個(gè)屏幕位置拖動(dòng)到另一個(gè)屏幕位置废麻。 在iPhone
上荠卷,拖放僅在應(yīng)用程序內(nèi)可用,而在iPad
上烛愧,它也可以跨應(yīng)用程序使用油宜。 這對(duì)于快速將Photos
中的圖像添加到電子郵件中非常方便掂碱。
在本教程中,您將通過構(gòu)建CacheManager
來探索拖放慎冤,這兩個(gè)應(yīng)用程序用于管理地理緩存:
CacheMaker
在正在進(jìn)行和已完成項(xiàng)目的看板中組織地理藏寶疼燥。 CacheEditor
允許用戶編輯從CacheMaker
帶來的geocache
的詳細(xì)信息。 您將通過向兩個(gè)應(yīng)用添加拖放支持來實(shí)現(xiàn)這些管理功能蚁堤。
在Xcode中打開CacheManager.xcworkspace
并選擇CacheMaker
作為active scheme
:
構(gòu)建并運(yùn)行CacheMaker
醉者。 您應(yīng)該看到兩個(gè)collection views
,其中第一個(gè)包含用于正在進(jìn)行的工作的地理緩存:
嘗試將geocache
從正在進(jìn)行的欄拖動(dòng)到完成的欄:
在本教程的第一部分中披诗,您的目標(biāo)是實(shí)現(xiàn)這一目標(biāo)撬即。 稍后,您將解鎖將geocaches
拖放到CacheEditor
另外一個(gè)應(yīng)用程序或者反方向拖出的能力呈队。
看看Xcode中的關(guān)鍵CacheMaker
文件:
-
CachesDataSource.swift:表示地理緩存
(geocaches)
集合視圖的數(shù)據(jù)源剥槐。 - CachesViewController.swift:顯示地理緩存的看板。
這些是您將要使用的文件宪摧,用于添加所需的功能粒竖。
Drag and Drop Overview
從源應(yīng)用程序拖動(dòng)項(xiàng)目時(shí),拖動(dòng)活動(dòng)(drag activity)
將開始绍刮,系統(tǒng)將創(chuàng)建拖動(dòng)會(huì)話(drag session)
温圆。 源應(yīng)用程序(source app)
設(shè)置拖動(dòng)項(xiàng)(drag item)
以在拖動(dòng)活動(dòng)開始時(shí)表示基礎(chǔ)數(shù)據(jù)。 在目標(biāo)應(yīng)用(destination app)
中放置該項(xiàng)結(jié)束拖動(dòng)活動(dòng)孩革。
拖動(dòng)項(xiàng)目封裝在項(xiàng)目提供程序(item provider)
中岁歉,該項(xiàng)目提供程序描述源應(yīng)用程序可以提供的數(shù)據(jù)類型。刪除項(xiàng)目后膝蜈,目標(biāo)應(yīng)用程序會(huì)以可以使用的格式請(qǐng)求項(xiàng)目锅移。
Apple自動(dòng)支持拖放文本視圖和文本字段(text views and text fields)
。 它還為表視圖和集合視圖(table views and collection views)
提供專用API饱搏。 您也可以添加拖放到自定義視圖嘶炭。
在本教程中,您將在集合視圖(collection views)
和自定義視圖中探索拖放呛牲。
Adding Drag Support
轉(zhuǎn)到CachesDataSource.swift
并將以下擴(kuò)展名添加到文件末尾:
extension CachesDataSource {
func dragItems(for indexPath: IndexPath) -> [UIDragItem] {
let geocache = geocaches[indexPath.item]
let itemProvider = NSItemProvider(object: geocache.name as NSString)
let dragItem = UIDragItem(itemProvider: itemProvider)
return [dragItem]
}
}
在這里议泵,您可以從geocache
名稱的NSString
表示創(chuàng)建項(xiàng)目提供程序(item provider)
。 然后返回一個(gè)包含此項(xiàng)目提供程序的拖動(dòng)項(xiàng)目(drag item)
的數(shù)組鬓催。
接下來肺素,打開CachesViewController.swift
并將以下內(nèi)容添加到文件末尾:
extension CachesViewController: UICollectionViewDragDelegate {
func collectionView(_ collectionView: UICollectionView,
itemsForBeginning session: UIDragSession,
at indexPath: IndexPath) -> [UIDragItem] {
let dataSource = dataSourceForCollectionView(collectionView)
return dataSource.dragItems(for: indexPath)
}
}
您采用UICollectionViewDragDelegate
并實(shí)現(xiàn)在拖動(dòng)活動(dòng)開始時(shí)調(diào)用的required
方法。 您的實(shí)現(xiàn)獲取collection view
的數(shù)據(jù)源宇驾,然后返回所選項(xiàng)的相應(yīng)拖動(dòng)項(xiàng)倍靡。
在collection view
代理賦值后,將以下內(nèi)容添加到viewDidLoad()
:
collectionView.dragDelegate = self
這使視圖控制器成為drag delegate
课舍。
構(gòu)建并運(yùn)行應(yīng)用程序塌西。 點(diǎn)擊并按住代表地理緩存的集合視圖單元格他挎。 點(diǎn)擊的單元應(yīng)該上升,允許您拖動(dòng)它:
請(qǐng)注意捡需,雖然您可以拖動(dòng)項(xiàng)目办桨,但不能將其放在任何位置。 嘗試這樣做只會(huì)將其放回原點(diǎn)站辉。
在CacheMaker
旁邊的Split View
中打開Reminders
崔挖。 您應(yīng)該能夠拖動(dòng)geocache
并將其放入Reminders
:
Reminders
可以接受geocache
名稱的導(dǎo)出NSString
表示,并使用它來創(chuàng)建新reminder
庵寞。
現(xiàn)在嘗試將Reminders
中的任何文本拖動(dòng)到CacheMaker
中狸相。 什么都沒發(fā)生。 那是因?yàn)槟鷽]有向CacheMaker
添加drop
支持捐川。 你接下來要解決這個(gè)問題脓鹃。
Adding Drop Support
轉(zhuǎn)到CachesDataSource.swift
并將以下內(nèi)容添加到CachesDataSource
擴(kuò)展:
func addGeocache(_ newGeocache: Geocache, at index: Int) {
geocaches.insert(newGeocache, at: index)
}
這會(huì)向數(shù)據(jù)源添加新的geocache
。
切換到CachesViewController.swift
并將以下協(xié)議擴(kuò)展添加到結(jié)尾:
extension CachesViewController: UICollectionViewDropDelegate {
func collectionView(
_ collectionView: UICollectionView,
performDropWith coordinator: UICollectionViewDropCoordinator) {
// 1
let dataSource = dataSourceForCollectionView(collectionView)
// 2
let destinationIndexPath =
IndexPath(item: collectionView.numberOfItems(inSection: 0), section: 0)
// 3
let item = coordinator.items[0]
// 4
switch coordinator.proposal.operation
{
case .copy:
print("Copying...")
let itemProvider = item.dragItem.itemProvider
// 5
itemProvider.loadObject(ofClass: NSString.self) { string, error in
if let string = string as? String {
// 6
let 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(at: [destinationIndexPath])
}
}
}
default:
return
}
}
}
在這里古沥,您采用UICollectionViewDropDelegate
協(xié)議瘸右。 然后,您可以實(shí)現(xiàn)在用戶結(jié)束拖動(dòng)活動(dòng)時(shí)調(diào)用的required
方法岩齿。 你的實(shí)現(xiàn):
- 1) 獲取集合視圖
(collection view)
的數(shù)據(jù)源太颤。 - 2) 將集合視圖的結(jié)尾設(shè)置為項(xiàng)目放置目標(biāo)。
- 3) 選擇第一個(gè)拖動(dòng)項(xiàng)盹沈。
- 4) 檢查你打算如何處理
drop
龄章。 - 5) 異步獲取拖動(dòng)項(xiàng)目的數(shù)據(jù)。
- 6) 使用基于傳入字符串?dāng)?shù)據(jù)的名稱創(chuàng)建新的
geocache
乞封。 - 7) 將新
geocache
添加到數(shù)據(jù)源做裙。 - 8) 在集合視圖
(collection view)
中插入新項(xiàng)。 您在主線程上調(diào)用此方法肃晚,因?yàn)閿?shù)據(jù)提取完成塊在內(nèi)部隊(duì)列上運(yùn)行锚贱。
1. Responding to Drops
將以下內(nèi)容添加到UICollectionViewDropDelegate
擴(kuò)展的末尾:
func collectionView(
_ collectionView: UICollectionView,
dropSessionDidUpdate session: UIDropSession,
withDestinationIndexPath destinationIndexPath: IndexPath?
) -> UICollectionViewDropProposal {
if session.localDragSession != nil {
return UICollectionViewDropProposal(operation: .forbidden)
} else {
return UICollectionViewDropProposal(
operation: .copy,
intent: .insertAtDestinationIndexPath)
}
}
您指定對(duì)正在拖動(dòng)的項(xiàng)目的響應(yīng)。 這包括向用戶提供視覺反饋关串。
這里的代碼禁止在應(yīng)用程序中進(jìn)行拖放。 它建議從另一個(gè)應(yīng)用程序中dropped
項(xiàng)目的復(fù)制操作晋修。
分配drag delegate
后吧碾,將以下內(nèi)容添加到viewDidLoad()
:
collectionView.dropDelegate = self
這會(huì)將view controller
設(shè)置為drop delegate
。
構(gòu)建并運(yùn)行應(yīng)用程序飞蚓。 使用Split View
中的Reminders
滤港,驗(yàn)證您是否可以將reminder
拖動(dòng)到正在進(jìn)行的集合視圖中:
如果您嘗試拖放到列表中間廊蜒,您將看到它只會(huì)添加到列表的末尾趴拧。 你以后會(huì)改進(jìn)這個(gè)溅漾。
嘗試在應(yīng)用內(nèi)拖放geocache
。 驗(yàn)證您是否獲得了不允許的視覺提示:
這并不理想著榴,所以你接下來就會(huì)繼續(xù)工作添履。
Drag and Drop in the Same App
仍然在CachesViewController.swift
中,轉(zhuǎn)到collectionView(_:dropSessionDidUpdate:withDestinationIndexPath :)
并使用以下代碼替換forbidden return
語句:
guard session.items.count == 1 else {
return UICollectionViewDropProposal(operation: .cancel)
}
if collectionView.hasActiveDrag {
return UICollectionViewDropProposal(operation: .move,
intent: .insertAtDestinationIndexPath)
} else {
return UICollectionViewDropProposal(operation: .copy,
intent: .insertAtDestinationIndexPath)
}
如果選擇了多個(gè)項(xiàng)目脑又,代碼將取消drop
暮胧。 對(duì)于單個(gè)drop item
,如果您在同一個(gè)collection view
中问麸,則建議移動(dòng)往衷。 否則,你提出一份copy
严卖。
在CachesDataSource.swift
中席舍,將以下方法添加到擴(kuò)展:
func moveGeocache(at sourceIndex: Int, to destinationIndex: Int) {
guard sourceIndex != destinationIndex else { return }
let geocache = geocaches[sourceIndex]
geocaches.remove(at: sourceIndex)
geocaches.insert(geocache, at: destinationIndex)
}
這會(huì)在數(shù)據(jù)源中重新定位geocache
。
返回CachesViewController.swift
并在collectionView(_:performDropWith :)
中使用以下內(nèi)容替換destinationIndexPath
賦值:
let destinationIndexPath: IndexPath
if let indexPath = coordinator.destinationIndexPath {
destinationIndexPath = indexPath
} else {
destinationIndexPath = IndexPath(
item: collectionView.numberOfItems(inSection: 0),
section: 0)
}
在這里哮笆,檢查index path
来颤,指定插入項(xiàng)目的位置。 如果未找到稠肘,則項(xiàng)目將在collection view
的末尾插入福铅。
在.copy
case
之前添加以下內(nèi)容:
case .move:
print("Moving...")
// 1
if let sourceIndexPath = item.sourceIndexPath {
// 2
collectionView.performBatchUpdates({
dataSource.moveGeocache(
at: sourceIndexPath.item,
to: destinationIndexPath.item)
collectionView.deleteItems(at: [sourceIndexPath])
collectionView.insertItems(at: [destinationIndexPath])
})
// 3
coordinator.drop(item.dragItem, toItemAt: destinationIndexPath)
}
這段代碼:
- 1) 獲取您應(yīng)該有權(quán)訪問的源
index path
,以便在同一個(gè)集合視圖中進(jìn)行拖放项阴。 - 2) 執(zhí)行批量更新以在數(shù)據(jù)源和集合視圖中移動(dòng)
geocache
滑黔。 - 3) 在集合視圖中動(dòng)畫插入拖動(dòng)的
geocache
。
1. Follow My Moves
構(gòu)建并運(yùn)行應(yīng)用程序环揽。 驗(yàn)證在集合視圖中拖放geocache
是否會(huì)創(chuàng)建副本并記錄副本消息:
測試您還可以在同一個(gè)集合視圖中移動(dòng)geocache
并查看記錄的移動(dòng)消息:
在集合視圖中拖放時(shí)拷沸,您可能已經(jīng)發(fā)現(xiàn)了一些效率低下的問題。 您正在使用同一個(gè)應(yīng)用程序薯演,但您正在創(chuàng)建該對(duì)象的低保真副本撞芍。 更不用說,你正在創(chuàng)建一個(gè)副本跨扮!
當(dāng)然序无,你可以做得更好。
Optimizing the Drop Experience
您可以進(jìn)行一些優(yōu)化以改進(jìn)拖放實(shí)現(xiàn)和體驗(yàn)衡创。
1. Using In-Memory Data
您應(yīng)該利用您在同一個(gè)應(yīng)用程序中訪問完整geocache
結(jié)構(gòu)的權(quán)限帝嗡。
轉(zhuǎn)到CachesDataSource.swift
。 在return
語句之前直接將以下內(nèi)容添加到dragItems(for :)
:
dragItem.localObject = geocache
您將geocache
分配給drag item
屬性璃氢。 這樣可以在以后更快地檢索項(xiàng)目
轉(zhuǎn)到CachesViewController.swift
哟玷。 在collectionView(_:performDropWith :)
中,使用以下內(nèi)容替換.copy
case
中的代碼:
if let geocache = item.dragItem.localObject as? Geocache {
print("Copying from same app...")
dataSource.addGeocache(geocache, at: destinationIndexPath.item)
DispatchQueue.main.async {
collectionView.insertItems(at: [destinationIndexPath])
}
} else {
print("Copying from different app...")
let itemProvider = item.dragItem.itemProvider
itemProvider.loadObject(ofClass: NSString.self) { string, error in
if let string = string as? String {
let geocache = Geocache(
name: string, summary: "Unknown", latitude: 0.0, longitude: 0.0)
dataSource.addGeocache(geocache, at: destinationIndexPath.item)
DispatchQueue.main.async {
collectionView.insertItems(at: [destinationIndexPath])
}
}
}
}
這里,處理從不同應(yīng)用程序中dropped
的項(xiàng)目的代碼沒有改變巢寡。 對(duì)于從同一個(gè)應(yīng)用程序復(fù)制的項(xiàng)目喉脖,您可以從localObject
獲取已保存的geocache
并使用它來創(chuàng)建新的geocache
。
構(gòu)建并運(yùn)行應(yīng)用程序抑月。 驗(yàn)證跨collections view
的拖放現(xiàn)在復(fù)制geocache
結(jié)構(gòu):
2. Moving Items Across Collection Views
您現(xiàn)在可以更好地表示geocache
树叽。 這很好,但你真的應(yīng)該在collection views
中移動(dòng)geocache
而不是復(fù)制它谦絮。
仍然在CachesViewController.swift
中题诵,使用以下內(nèi)容替換collectionView(_:dropSessionDidUpdate:withDestinationIndexPath :)
實(shí)現(xiàn):
guard session.localDragSession != nil else {
return UICollectionViewDropProposal(
operation: .copy,
intent: .insertAtDestinationIndexPath)
}
guard session.items.count == 1 else {
return UICollectionViewDropProposal(operation: .cancel)
}
return UICollectionViewDropProposal(
operation: .move,
intent: .insertAtDestinationIndexPath)
您現(xiàn)在可以在與移動(dòng)操作同一個(gè)應(yīng)用程序中處理drops
。
轉(zhuǎn)到File ? New ? File…
并選擇iOS ? Source ? Swift File
模板层皱。 點(diǎn)擊Next
性锭。 將文件命名為CacheDragCoordinator.swift
,然后單擊Create
叫胖。
在文件末尾添加以下內(nèi)容:
class CacheDragCoordinator {
let sourceIndexPath: IndexPath
var dragCompleted = false
var isReordering = false
init(sourceIndexPath: IndexPath) {
self.sourceIndexPath = sourceIndexPath
}
}
您已經(jīng)創(chuàng)建了一個(gè)類來協(xié)調(diào)同一個(gè)應(yīng)用程序中的拖放篷店。 在這里設(shè)置要跟蹤的屬性:
- 拖動(dòng)開始的地方。
- 什么時(shí)候完成臭家。
- 集合視圖項(xiàng)應(yīng)該在
drop
后重新排序疲陕。
切換到CachesDataSource.swift
并將以下方法添加到擴(kuò)展:
func deleteGeocache(at index: Int) {
geocaches.remove(at: index)
}
此方法刪除指定索引處的geocache
。 重新排序集合視圖項(xiàng)時(shí)钉赁,您將使用此幫助方法蹄殃。
轉(zhuǎn)到CachesViewController.swift
。 在return
語句之前直接將以下內(nèi)容添加到collectionView(_:itemsForBeginning:at)
:
let dragCoordinator = CacheDragCoordinator(sourceIndexPath: indexPath)
session.localContext = dragCoordinator
在這里你踩,使用起始index path
初始化drag coordinator
诅岩。 然后,將此對(duì)象添加到存儲(chǔ)自定義數(shù)據(jù)的drag
會(huì)話屬性带膜。 此數(shù)據(jù)僅對(duì)拖動(dòng)活動(dòng)開始的應(yīng)用程序可見吩谦。
3. Are You My App?
找到collectionView(_:performDropWith :)
。 使用以下內(nèi)容替換.copy
case
中的代碼:
print("Copying from different app...")
let itemProvider = item.dragItem.itemProvider
itemProvider.loadObject(ofClass: NSString.self) { string, error in
if let string = string as? String {
let geocache = Geocache(
name: string, summary: "Unknown", latitude: 0.0, longitude: 0.0)
dataSource.addGeocache(geocache, at: destinationIndexPath.item)
DispatchQueue.main.async {
collectionView.insertItems(at: [destinationIndexPath])
}
}
}
您已將復(fù)制路徑簡化為僅處理來自其他應(yīng)用的drops
膝藕。
用以下內(nèi)容替換.move
case
中的代碼:
// 1
guard let dragCoordinator =
coordinator.session.localDragSession?.localContext as? CacheDragCoordinator
else { return }
// 2
if let sourceIndexPath = item.sourceIndexPath {
print("Moving within the same collection view...")
// 3
dragCoordinator.isReordering = true
// 4
collectionView.performBatchUpdates({
dataSource.moveGeocache(at: sourceIndexPath.item, to: destinationIndexPath.item)
collectionView.deleteItems(at: [sourceIndexPath])
collectionView.insertItems(at: [destinationIndexPath])
})
} else {
print("Moving between collection views...")
// 5
dragCoordinator.isReordering = false
// 6
if let geocache = item.dragItem.localObject as? Geocache {
collectionView.performBatchUpdates({
dataSource.addGeocache(geocache, at: destinationIndexPath.item)
collectionView.insertItems(at: [destinationIndexPath])
})
}
}
// 7
dragCoordinator.dragCompleted = true
// 8
coordinator.drop(item.dragItem, toItemAt: destinationIndexPath)
這是對(duì)正在發(fā)生的事情的逐步細(xì)分:
- 1) 獲得
drag coordinator
式廷。 - 2) 檢查是否設(shè)置了拖動(dòng)項(xiàng)的源索引路徑
source index path
。 這意味著拖放位于同一個(gè)集合視圖中芭挽。 - 3) 通知
drag coordinator
將重新排序集合視圖滑废。 - 4) 執(zhí)行批量更新以在數(shù)據(jù)源和集合視圖中移動(dòng)
geocache
。 - 5) 請(qǐng)注意袜爪,集合視圖不會(huì)被重新排序蠕趁。
- 6) 檢索本地存儲(chǔ)的
geocache
。 將其添加到數(shù)據(jù)源并將其插入到集合視圖中辛馆。 - 7) 讓
drag coordinator
知道拖動(dòng)完成了俺陋。 - 8) 動(dòng)畫在集合視圖中拖動(dòng)的
geocache
的插入。
將以下方法添加到您的UICollectionViewDragDelegate
擴(kuò)展:
func collectionView(_ collectionView: UICollectionView,
dragSessionDidEnd session: UIDragSession) {
// 1
guard
let dragCoordinator = session.localContext as? CacheDragCoordinator,
dragCoordinator.dragCompleted == true,
dragCoordinator.isReordering == false
else {
return
}
// 2
let dataSource = dataSourceForCollectionView(collectionView)
let sourceIndexPath = dragCoordinator.sourceIndexPath
// 3
collectionView.performBatchUpdates({
dataSource.deleteGeocache(at: sourceIndexPath.item)
collectionView.deleteItems(at: [sourceIndexPath])
})
}
當(dāng)拖動(dòng)中止或drop
項(xiàng)目時(shí),將調(diào)用此方法腊状。 這是代碼的作用:
- 1) 檢查
drag coordinator
诱咏。 如果drop
完成且集合視圖未重新排序,則繼續(xù)寿酌。 - 2) 獲取數(shù)據(jù)源和源索引路徑以準(zhǔn)備更新。
- 3) 執(zhí)行批量更新以從數(shù)據(jù)源和集合視圖中刪除
geocache
硕蛹。 回想一下醇疼,您之前已將相同的geocache
添加到drop destination
。 這需要將其從drag source
上移除法焰。
構(gòu)建并運(yùn)行應(yīng)用程序秧荆。 驗(yàn)證在集合視圖中移動(dòng)實(shí)際上是否移動(dòng)項(xiàng)目并在控制臺(tái)打印Moving between collection views...
:
4. Adding a Placeholder
從外部應(yīng)用程序獲取項(xiàng)目并將其加載到目標(biāo)應(yīng)用程序中可能需要一些時(shí)間。 向用戶提供視覺反饋(例如顯示占位符)是一種很好的做法埃仪。
用以下代碼替換collectionView(_:performDropWith :)
中的.copy
case
:
print("Copying from different app...")
// 1
let placeholder = UICollectionViewDropPlaceholder(
insertionIndexPath: destinationIndexPath, reuseIdentifier: "CacheCell")
// 2
placeholder.cellUpdateHandler = { cell in
if let cell = cell as? CacheCell {
cell.cacheNameLabel.text = "Loading..."
cell.cacheSummaryLabel.text = ""
cell.cacheImageView.image = nil
}
}
// 3
let context = coordinator.drop(item.dragItem, to: placeholder)
let itemProvider = item.dragItem.itemProvider
itemProvider.loadObject(ofClass: NSString.self) { string, error in
if let string = string as? String {
let 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)
})
}
}
}
這是正在發(fā)生的事情:
- 1) 為新內(nèi)容創(chuàng)建占位符單元格乙濒。
- 2) 定義配置占位符單元格的
block
。 - 3) 將占位符插入集合視圖中卵蛉。
- 4) 提交插入以將占位符與最終單元格交換颁股。
構(gòu)建并運(yùn)行應(yīng)用程序。 從Reminders
中拖放項(xiàng)目傻丝。 將項(xiàng)目放入集合視圖時(shí)甘有,請(qǐng)注意占位符文本的簡要外觀:
Multiple Data Representations
您可以配置可以傳遞到目標(biāo)應(yīng)用程序或從源應(yīng)用程序使用的數(shù)據(jù)類型。
使用init(object :)
創(chuàng)建項(xiàng)目提供程序item provider
時(shí)葡缰,傳入的對(duì)象必須符合NSItemProviderWriting
亏掀。 采用該協(xié)議包括為您可以導(dǎo)出的數(shù)據(jù)指定統(tǒng)一類型標(biāo)識(shí)符(uniform type identifiers - UTI)
并處理每個(gè)數(shù)據(jù)表示的導(dǎo)出。
例如泛释,您可能希望為僅接受字符串的應(yīng)用導(dǎo)出geocache
的字符串表示形式滤愕。 或者您可能想要導(dǎo)出照片應(yīng)用的圖像表示。 對(duì)于您控制下使用地理位置的應(yīng)用怜校,您可能希望導(dǎo)出完整的數(shù)據(jù)模型间影。
要正確使用已dropped
的項(xiàng)并將其轉(zhuǎn)換為geocaches
,您的數(shù)據(jù)模型應(yīng)采用NSItemProviderReading
茄茁。 然后宇智,您可以實(shí)現(xiàn)協(xié)議方法以指定可以使用的數(shù)據(jù)表示形式。 您還將實(shí)現(xiàn)它們以指定如何根據(jù)源應(yīng)用程序發(fā)送的內(nèi)容強(qiáng)制傳入數(shù)據(jù)胰丁。
到目前為止随橘,在應(yīng)用程序之間拖放地理緩存時(shí),您已經(jīng)使用過字符串锦庸。 NSString
自動(dòng)支持NSItemProviderWriting
和NSItemProviderReading
机蔗,因此您不必編寫任何特殊代碼。
要處理多種數(shù)據(jù)類型,您將更改geocache
數(shù)據(jù)模型萝嘁。 您可以在Geocache
項(xiàng)目中找到它梆掸,它是您打開的Xcode
工作區(qū)的一部分。
在Geocache
項(xiàng)目中牙言,打開Geocache.swift
并在Foundation import
后添加以下內(nèi)容:
import MobileCoreServices
您需要此框架來使用預(yù)定義的UTI
酸钦,例如代表PNG
的UTI
。
在上次導(dǎo)入后立即添加以下內(nèi)容:
public let geocacheTypeId = "com.xxxxx.geocache"
您創(chuàng)建一個(gè)代表geocache
的自定義字符串標(biāo)識(shí)符咱枉。
1. Reading and Writing Geocaches
將以下擴(kuò)展名添加到文件末尾:
extension Geocache: NSItemProviderWriting {
// 1
public static var writableTypeIdentifiersForItemProvider: [String] {
return [geocacheTypeId,
kUTTypePNG as String,
kUTTypePlainText as String]
}
// 2
public func loadData(
withTypeIdentifier typeIdentifier: String,
forItemProviderCompletionHandler completionHandler:
@escaping (Data?, Error?) -> Void)
-> Progress? {
if typeIdentifier == kUTTypePNG as String {
// 3
if let image = image {
completionHandler(image, nil)
} else {
completionHandler(nil, nil)
}
} else if typeIdentifier == kUTTypePlainText as String {
// 4
completionHandler(name.data(using: .utf8), nil)
} else if typeIdentifier == geocacheTypeId {
// 5
do {
let archiver = NSKeyedArchiver(requiringSecureCoding: false)
try archiver.encodeEncodable(self, forKey: NSKeyedArchiveRootObjectKey)
archiver.finishEncoding()
let data = archiver.encodedData
completionHandler(data, nil)
} catch {
completionHandler(nil, nil)
}
}
return nil
}
}
在這里卑硫,您符合NSItemProviderWriting
并執(zhí)行以下操作:
- 1) 指定可以傳送到目標(biāo)應(yīng)用程序的數(shù)據(jù)表示。 您希望返回從對(duì)象的最高保真度版本到最低值排序的字符串?dāng)?shù)組蚕断。
- 2) 實(shí)現(xiàn)在請(qǐng)求時(shí)將數(shù)據(jù)傳遞到目標(biāo)應(yīng)用程序的方法欢伏。 系統(tǒng)在
dropped
項(xiàng)目時(shí)會(huì)調(diào)用此方法并傳入適當(dāng)?shù)念愋蜆?biāo)識(shí)符。 - 3) 如果傳入
PNG
標(biāo)識(shí)符亿乳,則在完成處理程序中返回geocache
的圖像硝拧。 - 4) 如果傳入文本標(biāo)識(shí)符,則在完成處理程序中返回
geocache
的名稱葛假。 - 5) 如果傳入自定義
geocache
類型標(biāo)識(shí)符障陶,則返回與整個(gè)geocache
對(duì)應(yīng)的數(shù)據(jù)對(duì)象。
現(xiàn)在聊训,在分配geocacheTypeId
后立即添加以下枚舉:
enum EncodingError: Error {
case invalidData
}
當(dāng)讀取數(shù)據(jù)時(shí)出現(xiàn)問題咸这,您將使用它來返回錯(cuò)誤代碼。
接下來魔眨,將以下內(nèi)容添加到文件末尾:
extension Geocache: NSItemProviderReading {
// 1
public static var readableTypeIdentifiersForItemProvider: [String] {
return [geocacheTypeId,
kUTTypePlainText as String]
}
// 2
public static func object(withItemProviderData data: Data,
typeIdentifier: String) throws -> Self {
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 let geocache =
try unarchiver.decodeTopLevelDecodable(
Geocache.self, forKey: NSKeyedArchiveRootObjectKey) else {
throw EncodingError.invalidData
}
return self.init(geocache)
} catch {
throw EncodingError.invalidData
}
} else {
throw EncodingError.invalidData
}
}
}
在這里媳维,您遵循NSItemProviderReading
以指定如何處理傳入數(shù)據(jù)。 這是正在做的事情:
- 1) 指定模型可以使用的傳入數(shù)據(jù)的類型遏暴。 此處列出的
UTI
代表geocache
和文本侄刽。 - 2) 給定類型標(biāo)識(shí)符,實(shí)現(xiàn)導(dǎo)入數(shù)據(jù)
required
的協(xié)議方法朋凉。 - 3) 對(duì)于文本標(biāo)識(shí)符州丹,使用基于傳入文本和占位符信息的名稱創(chuàng)建新的地理緩存。
- 4) 對(duì)于
geocache
標(biāo)識(shí)符杂彭,解碼傳入的數(shù)據(jù)并使用它來創(chuàng)建完整的geocache
模型墓毒。
錯(cuò)誤或無法識(shí)別的類型標(biāo)識(shí)符會(huì)引發(fā)您之前定義的錯(cuò)誤。
2. Back to My App
將active scheme
更改為Geocache
并構(gòu)建項(xiàng)目亲怠。 然后將active scheme
更改回CacheMaker
所计。
在CacheMaker
中,轉(zhuǎn)到CachesDataSource.swift
并在dragItems(for:)
內(nèi)部將itemProvider
賦值更改為:
let itemProvider = NSItemProvider(object: geocache)
在這里团秽,您可以使用geocache
初始化項(xiàng)目提供程序主胧,因?yàn)槟哪P筒捎?code>NSItemProviderWriting來正確導(dǎo)出數(shù)據(jù)叭首。
打開CachesViewController.swift
并找到collectionView(_:performDropWith :)
。 在.copy
case
下踪栋,使用以下內(nèi)容替換item provider
的loadObject
調(diào)用:
itemProvider.loadObject(ofClass: Geocache.self) { geocache, _ in
if let geocache = geocache as? Geocache {
DispatchQueue.main.async {
context.commitInsertion(dataSourceUpdates: {_ in
dataSource.addGeocache(geocache, at: destinationIndexPath.item)
})
}
}
}
您已修改了drop handler
以加載Geocache
類型的對(duì)象焙格。 完成塊現(xiàn)在返回一個(gè)可以直接使用的geocache
。
構(gòu)建并運(yùn)行應(yīng)用程序夷都。 如有必要眷唉,將Reminders
放在Split View
中。 檢查Reminders
和CacheMaker
之間的拖放項(xiàng)是否像以前一樣工作:
在Split View
中顯示Photos
以替換Reminders
囤官。 從正在進(jìn)行的通道拖動(dòng)地理緩存并將其放入Photos
以驗(yàn)證您是否可以導(dǎo)出地理緩存的圖像表示:
您可以使用臨時(shí)hack
測試完整數(shù)據(jù)模型導(dǎo)出路徑冬阳。 轉(zhuǎn)到CachesViewController.swift
并在collectionView(_:dropSessionDidUpdate:withDestinationIndexPath :)
中使用以下內(nèi)容替換返回移動(dòng)操作的行:
return UICollectionViewDropProposal(
operation: .copy,
intent: .insertAtDestinationIndexPath)
您在與復(fù)制操作相同的應(yīng)用程序中配置拖放。 這會(huì)觸發(fā)應(yīng)導(dǎo)出和導(dǎo)入完整數(shù)據(jù)模型的代碼治拿。
構(gòu)建并運(yùn)行應(yīng)用程序摩泪。 測試在應(yīng)用程序中移動(dòng)項(xiàng)目會(huì)生成geocache
的正確副本:
在collectionView(_:dropSessionDidUpdate:withDestinationIndexPath :)
中恢復(fù)臨時(shí)hack
笆焰,以便應(yīng)用程序內(nèi)拖放執(zhí)行移動(dòng)操作:
return UICollectionViewDropProposal(
operation: .move,
intent: .insertAtDestinationIndexPath)
構(gòu)建并運(yùn)行應(yīng)用程序以恢復(fù)到pre-hack
的狀態(tài)劫谅。
Adding Drag Support to a Custom View
您已經(jīng)了解了如何向集合視圖添加拖放支持。 將此支持添加到table views
遵循類似的過程嚷掠。
您還可以向自定義視圖添加拖放功能捏检。 基本步驟包括:
- 將交互對(duì)象添加到自定義視圖。
- 在交互委托中實(shí)現(xiàn)協(xié)議方法以提供或使用數(shù)據(jù)不皆。
是時(shí)候介紹用于編輯地理藏寶的伴侶應(yīng)用程序CacheEditor
贯城。 將active scheme
更改為
CacheEditor
。 構(gòu)建并運(yùn)行應(yīng)用程序霹娄,然后將設(shè)備旋轉(zhuǎn)到橫向模式:
在Split View
中查看CacheMaker
能犯,將其放在CacheEditor
的左側(cè)。 調(diào)整Split View
的大小犬耻,使兩個(gè)應(yīng)用程序占用大約一半的寬度:
嘗試將geocache
從CacheEditor
拖到CacheMaker
中踩晶。 我的朋友,你是一個(gè)令人沮喪的經(jīng)歷枕磁。
您將使用CacheEditor
中的一個(gè)關(guān)鍵文件CacheDetailViewController.swift
渡蜻,它顯示geocache
詳細(xì)信息。 打開該文件并將以下代碼添加到最后:
// 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 ]
}
}
在這里计济,您采用UIDragInteractionDelegate
并實(shí)現(xiàn)拖動(dòng)活動(dòng)開始時(shí)調(diào)用的方法茸苇。 代碼應(yīng)該與您在CacheMaker
中看到的類似。 您將帶有geocache
的拖動(dòng)項(xiàng)目作為item provider
返回沦寂。
在調(diào)用super
之后立即將以下內(nèi)容添加到viewDidLoad()
:
view.addInteraction(UIDragInteraction(delegate: self))
在這里学密,您將創(chuàng)建與作為代理的視圖控制器的拖動(dòng)交互。 然后传藏,您將交互添加到視圖中则果。
構(gòu)建并運(yùn)行CacheEditor
幔翰。 驗(yàn)證您現(xiàn)在可以從CacheEditor
拖動(dòng)地理緩存并將其放入CacheMaker
:
嘗試將地理緩存從CacheMaker
拖到CacheEditor
中。 拖動(dòng)開始時(shí)西壮,它不會(huì)drop
遗增。 這是你的下一個(gè)任務(wù)。
Adding Drop Support to a Custom View
仍然在CacheDetailViewController.swift
中款青,將以下內(nèi)容添加到文件的末尾:
// 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()
}
}
}
}
在這里做修,您采用UIDropInteractionDelegate
并實(shí)現(xiàn)可選optional
方法來跟蹤和正確處理drops
。
第一種方法將drop
限制為傳入Geocache
對(duì)象的應(yīng)用程序抡草。
第二種方法返回復(fù)制操作作為處理drop
的建議方法饰及。 當(dāng)用戶在drop
交互視圖上拖動(dòng)項(xiàng)目時(shí),將調(diào)用此方法康震。 雖然此協(xié)議方法是可選的燎含,您也需要實(shí)現(xiàn)它以接受drops
。
drop
手勢完成時(shí)調(diào)用最后一個(gè)協(xié)議方法腿短。 您從會(huì)話中獲取item provider
并啟動(dòng)數(shù)據(jù)提取屏箍。 然后,加載第一個(gè)geocache
并更新視圖橘忱。
接下來赴魁,在拖動(dòng)交互設(shè)置之后將此代碼添加到viewDidLoad()
:
view.addInteraction(UIDropInteraction(delegate: self))
通過此操作,您可以創(chuàng)建drop
交互并將視圖控制器設(shè)置為委托钝诚。 然后颖御,您將交互添加到視圖中。
構(gòu)建并運(yùn)行應(yīng)用程序凝颇。 驗(yàn)證您是否可以將geocache
drop
到CacheEditor
中:
只需很少的代碼行潘拱,您就可以向自定義視圖添加拖放支持。
恭喜拧略! 您已使用拖放操作來使geocache
管理示例應(yīng)用程序正常運(yùn)行芦岂。
您現(xiàn)在應(yīng)該能夠添加拖放功能,以增強(qiáng)許多應(yīng)用內(nèi)和跨應(yīng)用體驗(yàn)了辑鲤。
后記
本篇主要講述了基于UICollectionViews和Drag-Drop在兩個(gè)APP間的使用示例盔腔,感興趣的給個(gè)贊或者關(guān)注~~~