在iOS 11中使用Codable拖放自定义类
介绍
我最近遇到了一个有趣的问题。 我为客户(学校)创建了一个mac应用程序,您可以在其中创建和管理学校提供的教师资料和科目(它们会定期更改)。 我创建的Mac应用程序具有一个带复选框的简单表格视图,您可以在其中为教师分配主题。
我正在使用Firebase进行所有操作,当它们从2.x迁移到3.x时,他们放弃了对macOS的支持。 尽管现在由于社区的巨大努力而再次运行,但我还是觉得做原生iOS应用程序是最好的方法。
解
我想创建一种将主题从一个表视图拖到另一个表视图的方法,从而将该主题分配给老师,如下所示:
但是我遇到了一个问题,我想不出将所需数据从一个表视图传输到另一个表的最佳方法。 我可以将主题的id
发送到另一个表视图,但这意味着我将不得不重新获取主题或具有一些全局状态来查询。 我想要一个干净利落的解决方案。 输入:
NSItemProviderReading,NSItemProviderWriting和Codable
使用这三种协议,我可以将Subject类编码为数据类型,将其作为ItemProvider传递,并将其用作UIDragItem
,然后在另一端重新实例化。
当用户启动拖动时,将发生以下情况:
表格视图将使用此方法向UITableViewDragDelegate
询问UIDragItem
func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem]
此方法返回一个UIDragItems
数组。 用一个NSItemProvider
对象实例化一个NSItemProvider
,这是第一个挑战。 NSItemProviderWriting
仅采用几种数据类型(NSString,UIImage,NSTextStorage,CNContact来命名),这是我们为了使我们的类创建项目提供者对象而需要采用的协议。
采用NSItemProviderWriting很容易,只有两个要求要处理。 第一个是writableTypeIdentifiersForItemProvider
,您必须在其中指定对象可以表示为哪种数据类型,并使用func loadData(withTypeIdentifier typeIdentifier: String, forItemProviderCompletionHandler completionHandler: @escaping (Data?, Error?) -> Void) -> Progress?
这是处理将类转换为上述属性中指定的数据类型的方法。
这就是Codable协议的来源。有了Codable,我们可以将对象编码为json数据对象,然后将其传递给项目提供者,然后使用NSItemProviderReading
协议一致性在另一端对其进行解码。 如下所示,要使JSON编码和解码正常工作,您需要做的就是采用该协议。 显然,它有更高级的用法,您可能需要做更多的工作才能采用它,但是我将由其他人来解释。
//Subject.swift
最终课程主题:NSObject,
NSItemProviderWriting,
NSItemProviderReading,
可编码{
静态var writableTypeIdentifiersForItemProvider:[String] {
//我们知道我们想要将对象表示为数据类型,因此我们将指定
返回[(kUTTypeData as String)]
}
func loadData(withTypeIdentifier typeIdentifier:String,forItemProviderCompletionHandler completeHandler:@escaping(Data ?, Error?)-> Void)->进度? {
让进度=进度(totalUnitCount:100)
做{
//此处将对象编码为JSON数据对象,并发送给完成处理程序
让数据=尝试JSONEncoder()。encode(self)
progress.completedUnitCount = 100
completeHandler(data,nil)
} {
completeHandler(nil,错误)
}
返回进度
}
静态var visibleTypeIdentifiersForItemProvider:[String] {
//我们知道我们要接受对象作为数据表示形式,因此我们在此处指定
返回[(kUTTypeData)作为字符串]
}
//此函数实际上具有返回类型Self,但是当您尝试返回对象时,确实把事情弄糟了,因此,如果像我上面所做的那样将类标记为final,则可以将返回类型更改为返回您的班级类型。
静态函数对象(withItemProviderData数据:数据,类型标识符:字符串)抛出->主题{
让解码器= JSONDecoder()
做{
//这里我们将对象解码回其类表示形式并返回
让主题=尝试解码器。解码(Subject.self,来自:数据)
返回主题
} {
fatalError(错误)
}
}
让subjectId:字符串
var名称:String =“”,
imgUrl:字符串=“”,
// ...
覆盖init(){
subjectId = Database.database()。reference()。childByAutoId()。key
super.init()
}
初始化?(_快照:DataSnapshot){
// ...
}
}
现在,开始拖动会话非常简单:
func tableView(_ tableView:UITableView,itemsForBeginning会话:UIDragSession,在indexPath:IndexPath)-> [UIDragItem] {
让subject = subject [indexPath.row]
让itemProvider = NSItemProvider(对象:主题)
让dragItem = UIDragItem(itemProvider:itemProvider)
返回[dragItem]
}
并接受下降:
func tableView(_ tableView:UITableView,performDropWith协调器:UITableViewDropCoordinator){
用于coordinator.items中的项目{
item.dragItem.itemProvider.loadObject(ofClass:Subject.self,completeHandler:{(subject,error)in
如果让subject = subject as? 学科 {
//这是您的自定义对象
}
})
}
}
我发现此方法很酷,因为它感觉很本机,并且其他任何解决方案似乎都有些笨拙,并且会有一些解决方法。 我希望你也这样做。
克里斯