关于ios:如何观察共享应用程序组文件已更改

How to observe shared app groups file changed

我有在我的应用程序和扩展之间共享的文件:
从扩展名:

写入文件

1
2
3
4
5
6
7
8
9
10
11
12
13
func writeToFile()
{
    let file ="file.txt"
    let text ="data" //just a text
    let dir = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier:"group.com.ml.test.apps")!

        let fileURL = dir.appendingPathComponent(file)
        do {
            try text.write(to: fileURL, atomically: false, encoding: .utf8)
        }
        catch {/* error handling here */}

    }

从应用程序中读取:

1
2
3
4
5
6
7
8
9
10
11
12
func readFromFile()
{
    let file ="file.txt"
    let dir = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier:"group.com.ml.test.apps")!

        let fileURL = dir.appendingPathComponent(file)
        do {
            let text2 = try String(contentsOf: fileURL, encoding: .utf8)
            NSLog("Dan: \\(text2)")
        }
        catch {/* error handling here */}
    }

我的问题是如何观察该文件的更改。万一扩展名写了它并更改了数据,那么应用程序将获得通知更改并读取文件。


这是一个基于用法NSFileCoordinator/NSFilePresenter模式的方法的简单演示。

通过Xcode 11.4 / iOS 13.4测试

1)应用部分。这是ViewController扮演文件演示者的角色,为简单起见(如果一个控制器可以管理多个文件,则最好为每个文件创建显式演示者)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class ViewController: UIViewController, NSFilePresenter {
    var presentedItemURL: URL?
    var presentedItemOperationQueue: OperationQueue = OperationQueue.main


    @IBOutlet weak var userNameField: UILabel!

    func presentedItemDidChange() { // posted on changed existed file only
        readFromFile()
    }

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

        // register for presentedItemDidChange work
        NSFileCoordinator.addFilePresenter(self)
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)

        // unregister - required !!
        NSFileCoordinator.removeFilePresenter(self)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        let file ="file.txt"
        let dir = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier:"group.com.test.apps")!
        presentedItemURL = dir.appendingPathComponent(file)

        readFromFile() // read previously stored data
    }

    private func readFromFile()
    {
        let coordinator = NSFileCoordinator(filePresenter: self)
        coordinator.coordinate(readingItemAt: presentedItemURL!, options: [], error: nil) { url in
            if let text2 = try? String(contentsOf: url, encoding: .utf8) {
                userNameField.text = text2 // demo label in view for test
            } else {
                userNameField.text ="<no text>"
                //just initial creation of file needed to observe following changes
                coordinator.coordinate(writingItemAt: presentedItemURL!, options: .forReplacing, error: nil) { url in
                    do {
                        try"".write(to: url, atomically: false, encoding: .utf8)
                    }
                    catch { print("writing failed") }
                }
            }
        }
    }
}

2)扩展部分(具有一个按钮的演示的简单Today扩展)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class TodayViewController: UIViewController, NCWidgetProviding, NSFilePresenter {
    var presentedItemURL: URL?
    var presentedItemOperationQueue: OperationQueue = OperationQueue.main

    override func viewDidLoad() {
        super.viewDidLoad()

        let file ="file.txt"
        let dir = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier:"group.com.test.apps")!
        presentedItemURL = dir.appendingPathComponent(file)
    }

    @IBAction func post(_ sender: Any) { // action on button in extension
        writeToFile()
    }

    func writeToFile()
    {
        let text ="new data" //just a text
        let coordinator = NSFileCoordinator(filePresenter: self)
        coordinator.coordinate(writingItemAt: presentedItemURL!, options: .forReplacing, error: nil) { url in
            do {
                try text.write(to: url, atomically: false, encoding: .utf8)
            }
            catch { print("writing failed") }
        }
    }

    func widgetPerformUpdate(completionHandler: (@escaping (NCUpdateResult) -> Void)) {
        completionHandler(NCUpdateResult.newData)
    }

}