macOS平台Swift开发:如何跨窗口控制器调用函数?
兄弟,咱先把你遇到的两个问题逐个拆解解决——先搞定弹窗添加后列表不刷新的坑,再说说NSCollectionView动态编号的事儿。
你当前直接在PopupWindowVC里持有toDoListVC引用但调用无效,大概率是这个引用没被正确赋值(弹窗是独立窗口,主控制器的引用没传进去,导致toDoListVC是nil)。这里给你三个靠谱的解决方案,按推荐程度排序:
方案1:用闭包(Closure)实现回调
这是最简洁的方式,不用定义额外协议:
- 在
PopupWindowVC里添加闭包属性:
var onItemAdded: (() -> Void)?
- 当你在主控制器(比如
ToDoListViewController)弹出弹窗时,给这个闭包赋值:
let popupVC = PopupWindowVC() popupVC.onItemAdded = { [weak self] in self?.getToDoItems() // 这里直接调用刷新方法 } // 弹出弹窗的代码...
- 在
PopupWindowVC中保存Core Data成功后,调用闭包:
// 保存数据的代码... onItemAdded?() // 触发主控制器的刷新 self.close()
注意用
[weak self]避免循环引用,防止内存泄漏。
方案2:用NotificationCenter(通知中心)
适合跨层级、不直接关联的控制器通信:
- 在
ToDoListViewController的viewDidLoad()里注册通知:
NotificationCenter.default.addObserver(self, selector: #selector(getToDoItems), name: NSNotification.Name("ToDoItemAdded"), object: nil)
- 在
PopupWindowVC保存成功后发送通知:
// 保存数据的代码... NotificationCenter.default.post(name: NSNotification.Name("ToDoItemAdded"), object: nil) self.close()
- 别忘了在
ToDoListViewController的deinit里移除观察者:
deinit { NotificationCenter.default.removeObserver(self) }
方案3:代理模式(Protocol)
适合明确的控制器间通信场景:
- 定义一个协议:
protocol PopupDelegate: AnyObject { func didAddToDoItem() }
- 在
PopupWindowVC里添加代理属性:
weak var delegate: PopupDelegate?
- 让
ToDoListViewController遵守协议:
extension ToDoListViewController: PopupDelegate { func didAddToDoItem() { getToDoItems() } }
- 弹出弹窗时设置代理:
let popupVC = PopupWindowVC() popupVC.delegate = self // 弹出弹窗的代码...
- 保存成功后调用代理方法:
// 保存数据的代码... delegate?.didAddToDoItem() self.close()
你提到的NSTableView没有indexPath但NSCollectionView有的问题,其实动态编号的核心是根据单元格的位置动态计算编号,不管用不用NSFetchedResultsController都能实现:
基础实现(不用FetchedResultsController)
在NSCollectionView的数据源方法collectionView(_:itemForRepresentedObjectAt:)里,直接用indexPath.item来计算编号:
func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { let item = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier("ToDoItem"), for: indexPath) as! ToDoItemCollectionViewItem // 假设你的数据数组是toDoItems let todo = toDoItems[indexPath.item] item.titleLabel.stringValue = todo.title // 动态设置编号:indexPath.item从0开始,所以加1 item.numberLabel.stringValue = "\(indexPath.item + 1)" return item }
这样当你添加、删除项时,数据源方法会重新调用,编号会自动更新。
结合NSFetchedResultsController的实现
如果你用FetchedResultsController管理Core Data数据,同样在数据源方法里用indexPath.item计算编号:
func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { let item = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier("ToDoItem"), for: indexPath) as! ToDoItemCollectionViewItem let todo = fetchedResultsController.object(at: indexPath) item.titleLabel.stringValue = todo.title item.numberLabel.stringValue = "\(indexPath.item + 1)" return item }
FetchedResultsController会自动处理数据的增删改,触发数据源方法刷新,编号也会同步更新。
注意:如果你的数据有排序(比如按创建时间),
indexPath.item对应的就是排序后的位置,编号会和排序后的顺序一致,完全符合需求。
内容的提问来源于stack exchange,提问作者Ryan Whipple

