swift - 当我使用 Core Data 在 Swift 中拖放项目和标题时如何对它们进行排序,而 SourceIndexPath 似乎不起作用?
问题描述
当我使用 Core Data 在 Swift 中拖放它们并在使用索引路径时获取正确的项目时,如何正确排序我的项目和标题?
我遇到的问题是我的排序算法没有正确地在我的集合视图中拉出正确的行和部分(我在 stride 函数的行上得到索引超出范围错误)。我希望能够拖放行并将它们与它们的顺序一起保存以保存以及拖放标题以重新排序它们并保存它们的顺序。
为“上”和“下”变量分配值以及为我的核心数据实体的新订单属性获取值时似乎会出现此问题。尝试使用 SourceIndexPath.section 和 SourceIndexPath.item 获取拖动项时似乎存在问题。
查看我试图让拖放工作的位置。
import UIKit
class DetailViewController: UIViewController {
var fromItem: Item
init(item: Item) {
self.fromItem = item
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var dataSourceSnapshot = NSDiffableDataSourceSnapshot<Header, ListData>()
var collectionView: UICollectionView!
var dataSource: UICollectionViewDiffableDataSource<Header, ListData>!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemGroupedBackground
var layoutConfig = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
layoutConfig.headerMode = .firstItemInSection
let listLayout = UICollectionViewCompositionalLayout.list(using: layoutConfig)
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: listLayout)
view.addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor, constant: 0.0),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0.0),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0.0),
])
collectionView.dragDelegate = self
collectionView.dropDelegate = self
collectionView.dragInteractionEnabled = true
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addNewHeader))
let headers = fromItem.headers?.allObjects as? [Header] ?? []
print("headers here \(headers)")
dataSourceSnapshot.appendSections(headers.sorted(by: {$0.order < $1.order}))
dataSource.apply(dataSourceSnapshot)
for header in headers.sorted(by: {$0.order < $1.order}) {
let children = header.children!.allObjects as? [Child] ?? []
let sortedChildren = children.sorted{$0.order > $1.order}
var dataSourceSnapshot = NSDiffableDataSourceSectionSnapshot<ListData>()
let headerItem = ListData.header(header)
dataSourceSnapshot.append([headerItem])
for child in sortedChildren {
dataSourceSnapshot.append([.child(child)], to: headerItem)
}
dataSourceSnapshot.expand([headerItem])
dataSource.apply(dataSourceSnapshot, to: header, animatingDifferences: false)
}
}
@objc func addNewHeader() {
var order: Double = 0
let header = fromItem.headers?.allObjects as? [Header] ?? []
if header.count > 0 {
order = header.last!.order + 25
} else {
order = 100
}
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let context = appDelegate.persistentContainer.viewContext
let newHeader = Header(context: context)
newHeader.name = "Header"
newHeader.id = UUID()
newHeader.order = order
newHeader.item = fromItem
var childOrder: Double = 0
let firstChild = header.first(where: {$0.id == newHeader.id })?.children?.allObjects as? [Child] ?? []
if header.count > 0 && firstChild.count > 0 {
childOrder = firstChild.last!.order + 25
} else {
childOrder = 100
}
var snapshot = NSDiffableDataSourceSectionSnapshot<ListData>()
dataSourceSnapshot.appendSections([newHeader])
let headerItem = ListData.header(newHeader)
snapshot.append([headerItem])
var childOrderIncrement = childOrder
for i in 1...4 {
let child = Child(context: context)
child.order = childOrderIncrement
child.name = "child \(i)"
child.id = UUID()
child.header = newHeader
snapshot.append([.child(child)], to: headerItem)
childOrderIncrement += 25
}
snapshot.expand([headerItem])
dataSource.apply(snapshot, to: newHeader, animatingDifferences: false)
try? context.save()
print("Headers = \(fromItem.headers?.allObjects as? [Header] ?? [])")
}
}
extension DetailViewController: UICollectionViewDragDelegate {
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
guard let item = dataSource.itemIdentifier(for: indexPath) else {
collectionView.deselectItem(at: indexPath, animated: true)
return [UIDragItem]()
}
switch item {
case .header(let header):
let itemProvider = NSItemProvider(object: header.objectID.uriRepresentation().absoluteURL as NSURL)
let dragItem = UIDragItem(itemProvider: itemProvider)
return [dragItem]
case .child(let child):
let itemProvider = NSItemProvider(object: NSString(string: child.id!.uuidString))
let dragItem = UIDragItem(itemProvider: itemProvider)
return [dragItem]
}
}
func DetailViewController(_ collectionView: UICollectionView, itemsForAddingTo session: UIDragSession, at indexPath: IndexPath, point: CGPoint) -> [UIDragItem] {
guard let item = dataSource.itemIdentifier(for: indexPath) else {
collectionView.deselectItem(at: indexPath, animated: true)
return [UIDragItem]()
}
switch item {
case .header(_):
return [UIDragItem]()
case .child(let child):
let itemProvider = NSItemProvider(object: NSString(string: child.id!.uuidString))
let dragItem = UIDragItem(itemProvider: itemProvider)
return [dragItem]
}
}
}
extension DetailViewController: UICollectionViewDropDelegate {
private func reorderItems(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView) {
let items = coordinator.items
if items.count == 1, let item = items.first, let sourceIndexPath = item.sourceIndexPath {
var destIndexPath = destinationIndexPath
if destIndexPath.item >= collectionView.numberOfItems(inSection: destIndexPath.section) {
destIndexPath.item = collectionView.numberOfItems(inSection: destIndexPath.section) - 1
}
guard let fromChild = dataSource.itemIdentifier(for: sourceIndexPath),
sourceIndexPath != destIndexPath else { return }
var snap = dataSource.snapshot()
let header = dataSource.sectionIdentifier(for: destIndexPath.section)!
let sourceHeader = dataSource.sectionIdentifier(for: sourceIndexPath.section)!
switch fromChild {
case .header(_):
print("cannot reorder headers here")
case .child(let fromChild):
snap.deleteItems([.child(fromChild)])
if let toChild = dataSource.itemIdentifier(for: destIndexPath) {
switch toChild {
case .header(_):
print("cannot reorder headers here")
case .child(let toChild):
let isAfter = destIndexPath.item > sourceIndexPath.item
if isAfter {
if header == sourceHeader {
snap.insertItems([.child(fromChild)], afterItem: .child(toChild))
} else {
snap.insertItems([.child(fromChild)], afterItem: .child(toChild))
}
} else {
snap.insertItems([.child(fromChild)], beforeItem: .child(toChild))
}
}
} else {
snap.appendItems([.child(fromChild)], toSection: header)
}
}
var upper: Double
var lower: Double
let headers = fromItem.headers?.allObjects as? [Header] ?? []
let children = headers[destIndexPath.section].children!.allObjects as? [Child] ?? []
let sortedChildren = children.sorted{$0.order > $1.order}
if destIndexPath.item == sortedChildren.count {
print("Appending to the end of the list")
lower = sortedChildren.last?.order ?? 0
upper = sortedChildren.last?.order ?? 0 + 100.0
} else if destIndexPath.item == 0 {
print("Inserting into the beginning")
lower = 0.0
upper = sortedChildren.first?.order ?? 100.0
} else {
print("Inserting into the middle of the list")
upper = sortedChildren[destIndexPath.item - 2].order
lower = sortedChildren[destIndexPath.item - 1].order
}
print(upper)
print(lower)
if header != sourceHeader {
sortedChildren[sourceIndexPath.item].header = header
}
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
appDelegate.saveContext()
dataSource.apply(snap, animatingDifferences: false)
for header in headers.sorted(by: {$0.order < $1.order}) {
let children = header.children!.allObjects as? [Child] ?? []
let sortedChildren = children.sorted{$0.order > $1.order}
var dataSourceSnapshot = NSDiffableDataSourceSectionSnapshot<ListData>()
let headerItem = ListData.header(header)
dataSourceSnapshot.append([headerItem])
for child in sortedChildren {
dataSourceSnapshot.append([.child(child)], to: headerItem)
}
dataSourceSnapshot.expand([headerItem])
dataSource.apply(dataSourceSnapshot, to: header, animatingDifferences: false)
}
}
}
private func reorderHeaders(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView) {
let items = coordinator.items
if items.count == 1, let item = items.first, let sourceIndexPath = item.sourceIndexPath {
guard let fromHeader = dataSource.sectionIdentifier(for: sourceIndexPath.section),
sourceIndexPath != destinationIndexPath else { return }
var snap = dataSource.snapshot()
let unsortedHeaders = fromItem.headers?.allObjects as? [Header] ?? []
let headers = unsortedHeaders.sorted{$0.order > $1.order}
let children = fromHeader.children!.allObjects as? [Child] ?? []
let sortedChildren = children.sorted{$0.order > $1.order}
for i in sortedChildren {
snap.deleteItems([.child(i)])
}
snap.deleteSections([fromHeader])
if let toHeader = dataSource.sectionIdentifier(for: destinationIndexPath.section) {
let isAfter = destinationIndexPath.section > sourceIndexPath.section
if isAfter {
snap.insertSections([fromHeader], afterSection: toHeader)
} else {
snap.insertSections([fromHeader], beforeSection: toHeader)
}
} else {
snap.appendSections([fromHeader])
}
var upper: Double
var lower: Double
if destinationIndexPath.item == headers.count {
print("Appending to the end of the list")
lower = headers.last!.order
upper = headers.last!.order + 100.0
} else if destinationIndexPath.item == 0 {
print("Inserting into the beginning")
lower = 0.0
upper = headers.first?.order ?? 100.0
} else {
print("Inserting into the middle of the list")
upper = headers[destinationIndexPath.section - 1].order
lower = headers[destinationIndexPath.section].order
}
headers[sourceIndexPath.section].order = stride(from: lower, to: upper, by: (upper - lower) / Double(2)).map{ $0 }[1]
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
appDelegate.saveContext()
for header in headers.sorted(by: {$0.order < $1.order}) {
let children = header.children!.allObjects as? [Child] ?? []
let sortedChildren = children.sorted{$0.order > $1.order}
var dataSourceSnapshot = NSDiffableDataSourceSectionSnapshot<ListData>()
let headerItem = ListData.header(header)
dataSourceSnapshot.append([headerItem])
for child in sortedChildren {
dataSourceSnapshot.append([.child(child)], to: headerItem)
}
dataSourceSnapshot.expand([headerItem])
dataSource.apply(dataSourceSnapshot, to: header, animatingDifferences: false)
}
}
}
func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
let destinationIndexPath: IndexPath
if let indexPath = coordinator.destinationIndexPath {
destinationIndexPath = indexPath
} else {
let section = collectionView.numberOfSections - 1
let row = collectionView.numberOfItems(inSection: section)
destinationIndexPath = IndexPath(item: row, section: section)
}
switch coordinator.proposal.operation {
case .move:
for item in coordinator.items {
if item.dragItem.localObject as! String == "child" {
coordinator.session.loadObjects(ofClass: NSString.self) { items in
DispatchQueue.main.async {
print("reordering the children")
self.reorderItems(coordinator: coordinator, destinationIndexPath: destinationIndexPath, collectionView: collectionView)
}
}
} else {
coordinator.session.loadObjects(ofClass: NSURL.self) { items in
DispatchQueue.main.async {
print("reordering the headers")
self.reorderHeaders(coordinator: coordinator, destinationIndexPath: destinationIndexPath, collectionView: collectionView)
}
}
}
}
break
case .copy:
return
default:
return
}
}
func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {
return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
}
}
我的核心数据模型:
如果您还有其他问题,请随时提问。
如果它更容易,我可以把我的整个 Xcode 项目发给你。
解决方案
在我的应用程序中,我采用以下方式:
- 获取当前对象列表,按核心数据记录排序
UICollectionViewDragDelegate
从(collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath))中删除具有 indexPath 的对象UICollectionViewDropDelegate
将此对象从(collectionView(_collectionView:UICollectionView,performDropWith coordinator:UICollectionViewDropCoordinator))重新插入到目标indexPath的列表中- 使用更新的排序索引更新 Core Data 值索引
代码示例:
// MARK: - UICollectionViewDragDelegate
extension CombinedMainVC: UICollectionViewDragDelegate {
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
guard let cell = collectionView.cellForItem(at: indexPath) as? CalendarCC, let item = cell.calendar, let name = item.name else { return [UIDragItem]() }
startingIndexPath = indexPath
let itemProvider = NSItemProvider(object: name as NSString)
let dragItem = UIDragItem(itemProvider: itemProvider)
dragItem.localObject = item
return [dragItem]
}
}
// MARK: - UICollectionViewDropDelegate
extension CombinedMainVC: UICollectionViewDropDelegate {
func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {
let forbidden = UICollectionViewDropProposal(operation: .forbidden)
guard destinationIndexPath?.section == startingIndexPath.section else { return forbidden }
return collectionView.hasActiveDrag ? UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) : forbidden
}
func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
guard let destinationIndexPath = coordinator.destinationIndexPath else { return }
var calendars = fetchedRC.fetchedObjects!
let list = calendars.remove(at: startingIndexPath.row)
calendars.insert(list, at: destinationIndexPath.row)
for cal in fetchedRC.fetchedObjects! {
let sortIndex = calendars.firstIndex(of: cal)!
cal.secondData = Int32(sortIndex)
}
updated = false
appDelegate.saveContext()
postNotification("dateChanged")
}
}
(强制展开已完成,因为如果没有日历,则无法启动丢弃会话)
之后我的模型被更新,我可以将新快照应用到集合视图。我认为您应该在一个地方订购您的对象(例如,当您应用快照时)并且在拖放会话中您只需要更改您的数据模型并且不要触摸集合视图快照。
您可以对 Headers 和 Childs 使用相同的方法 - 唯一的区别在于您将在第 1 点中使用的对象列表
推荐阅读
- azure - PowerShell - 添加类型开关参数
- php - 从 codeigniter 中的视图发送动态电子邮件附件
- text-to-speech - Waveglow 音频质量问题
- windows - 计划任务中的 exe 文件返回代码“2147516556”
- php - 如何保存 PHP-MIME-MAIL-PARSE 捕获的文件并通过命令读取 fopen?拉拉维尔
- git - 多位作者的 Git GPG 签名
- hadoop - HIVE:异常:向现有外部表添加新分区时分区已存在
- rest - 而是收到 ResourceAccessException HttpServerErrorException
- terraform - 等待 ECS 集群的 Terraform 错误
- javascript - 为什么当我尝试在计算器中使用等于按钮时,eclipse 在线程错误中显示异常