今天的扩展:与容器应用程序同步数据

时间:2015-06-13 20:00:53

标签: ios swift ios-app-extension ios8-today-widget today-extension

上下文

我使用this example project一直在玩Today Extensions。

该应用非常简单:

  • 在包含应用中,您有一个待办事项列表,可以标记为已完成
  • 在“今日”窗口小部件中,您会看到相同的列表,但您可以使用分段控件在已完成和不完整的项目之间切换。

我的目标如下:无论何时在容器应用程序或小部件中发生数据更改,我都希望两者都反映这些更改:

  • 如果我在容器应用中将项目标记为已完成,请下拉通知中心,小部件应更新
  • 当我在小部件中执行相同操作时,然后返回应用程序,应用程序的状态应该更新

实施

我理解,容器应用程序和扩展程序在它们各自的进程中运行,这意味着两个约束:

  • NSUserDefaultsDidChangeNotification没用。
  • 在内存中管理模型实例是没用的。

我也知道,为了访问共享容器,两个目标都必须选择加入同一组ID下的应用程序组权利。

数据访问由嵌入式框架TodoKit管理。它不是将属性保存在内存中,而是直接到NSUserDefaults获取适当的值:

public struct ShoppingItemStore: ShoppingStoreType {

    private let defaultItems = [
        ShoppingItem(name: "Coffee"),
        ShoppingItem(name: "Banana"),
    ]

    private let defaults = NSUserDefaults(suiteName: appGroupId)

    public init() {}

    public func items() -> [ShoppingItem] {
        if let loaded = loadItems() {
            return loaded
        } else {
            return defaultItems
        }
    }

    public func toggleItem(item: ShoppingItem) {

        let initial = items()

        let updated = initial.map { original -> ShoppingItem in
            return original == item ?
                ShoppingItem(name: original.name, status: !original.status) : original
        }

        saveItems(updated)
    }

    private func saveItems(items: [ShoppingItem]) {

        let boxedItems = items.map { item -> [String : Bool] in
            return [item.name : item.status]
        }

        defaults?.setValue(boxedItems, forKey: savedDataKey)
        defaults?.synchronize()
    }

    private func loadItems() -> [ShoppingItem]? {

        if let loaded = defaults?.valueForKey(savedDataKey) as? [[String : Bool]] {

            let unboxed = loaded.map { dict -> ShoppingItem in

                return ShoppingItem(name: dict.keys.first!, status: dict.values.first!)
            }

            return unboxed
        }

        return nil
    }
}

问题

这是有效的:

  • 当我修改主应用程序中的列表,然后停止模拟器,然后从Xcode启动Today目标时,它反映了正确的状态。反之亦然。

这验证我的应用程序组设置正确。

但是,当我在主应用程序中更改某些内容时,请下拉通知中心,它完全不同步。这是我不明白的部分。

我的观点直接从共享容器中获取数据。每当发生更改时,我都会立即更新共享容器中的数据。

我错过了什么?我怎样才能正确同步这两个?我的数据访问类不是管理任何状态,但我不明白为什么它的行为不正确。

其他信息

  • 我知道MMWormhole。不幸的是,这不是我的选择,因为我需要在不包含任何第三方解决方案的情况下达到适当的功能。

  • 这个terrific article涵盖了主题,我可能需要使用NSFilePresenter,虽然看起来很麻烦,但我还没有完全理解这个机制。我真的希望,有一个比这个更容易的解决方案。

1 个答案:

答案 0 :(得分:2)

嗯,我在这里学到了两件事:

首先,始终仔细检查您的权利,我的某些方式搞砸了,这就是共享容器表现得如此笨拙的原因。

第二: 虽然未调用viewWillAppear(_:),但当您关闭通知中心时,仍然可以从您的应用代表触发更新:

func applicationDidBecomeActive(application: UIApplication) {
    NSNotificationCenter.defaultCenter().postNotificationName(updateDataNotification, object: nil)
}

然后在你的视图控制器中:

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)

    NSNotificationCenter.defaultCenter().addObserverForName(updateDataNotification, object: nil, queue: NSOperationQueue.mainQueue()) { (_) -> Void in
        self.tableView.reloadData()
    }
}

override func viewDidDisappear(animated: Bool) {
    super.viewDidDisappear(animated)
    NSNotificationCenter.defaultCenter().removeObserver(self)
}

更新“今日”窗口小部件很简单:每次下拉通知中心时,都会调用viewWillAppear(:_),因此您可以在那里查询新数据。

我很快就会在GitHub上更新示例项目。