在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? 学科 { 
//这是您的自定义对象
}
})
}
}

我发现此方法很酷,因为它感觉很本机,并且其他任何解决方案似乎都有些笨拙,并且会有一些解决方法。 我希望你也这样做。

克里斯