You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

macOS平台Swift开发:如何跨窗口控制器调用函数?

兄弟,咱先把你遇到的两个问题逐个拆解解决——先搞定弹窗添加后列表不刷新的坑,再说说NSCollectionView动态编号的事儿。

问题1:弹窗新增ToDo项后,主窗口列表不刷新

你当前直接在PopupWindowVC里持有toDoListVC引用但调用无效,大概率是这个引用没被正确赋值(弹窗是独立窗口,主控制器的引用没传进去,导致toDoListVC是nil)。这里给你三个靠谱的解决方案,按推荐程度排序:

方案1:用闭包(Closure)实现回调

这是最简洁的方式,不用定义额外协议:

  1. 在PopupWindowVC里添加闭包属性:
var onItemAdded: (() -> Void)?
  1. 当你在主控制器(比如ToDoListViewController)弹出弹窗时,给这个闭包赋值:
let popupVC = PopupWindowVC()
popupVC.onItemAdded = { [weak self] in
    self?.getToDoItems() // 这里直接调用刷新方法
}
// 弹出弹窗的代码...
  1. 在PopupWindowVC中保存Core Data成功后,调用闭包:
// 保存数据的代码...
onItemAdded?() // 触发主控制器的刷新
self.close()

注意用[weak self]避免循环引用,防止内存泄漏。

方案2:用NotificationCenter(通知中心)

适合跨层级、不直接关联的控制器通信:

  1. 在ToDoListViewController的viewDidLoad()里注册通知:
NotificationCenter.default.addObserver(self, selector: #selector(getToDoItems), name: NSNotification.Name("ToDoItemAdded"), object: nil)
  1. 在PopupWindowVC保存成功后发送通知:
// 保存数据的代码...
NotificationCenter.default.post(name: NSNotification.Name("ToDoItemAdded"), object: nil)
self.close()
  1. 别忘了在ToDoListViewController的deinit里移除观察者:
deinit {
    NotificationCenter.default.removeObserver(self)
}

方案3:代理模式(Protocol)

适合明确的控制器间通信场景:

  1. 定义一个协议:
protocol PopupDelegate: AnyObject {
    func didAddToDoItem()
}
  1. 在PopupWindowVC里添加代理属性:
weak var delegate: PopupDelegate?
  1. 让ToDoListViewController遵守协议:
extension ToDoListViewController: PopupDelegate {
    func didAddToDoItem() {
        getToDoItems()
    }
}
  1. 弹出弹窗时设置代理:
let popupVC = PopupWindowVC()
popupVC.delegate = self
// 弹出弹窗的代码...
  1. 保存成功后调用代理方法:
// 保存数据的代码...
delegate?.didAddToDoItem()
self.close()
问题2:NSCollectionView实现动态编号项

你提到的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 07:09:47