{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreib7hwsnhlxgz2guuc5yqbfnp7zfgywsb74z2xoc2ejwx4zktpd2t4",
"uri": "at://did:plc:e7ehpd5hzctp5oq7dlfbz6e5/app.bsky.feed.post/3me5oi3kejdw2"
},
"path": "/posts/open-recent-menu/",
"publishedAt": "2026-02-05T19:00:00.000Z",
"site": "https://swiftdevjournal.com",
"tags": [
"@Observable",
"@Environment",
"@Observable",
"@Observable",
"@Observable",
"@MainActor",
"@MainActor",
"@Observable",
"@Observable",
"@State",
"@Observable",
"@Environment",
"@Binding"
],
"textContent": "Why would you need to add an Open Recent menu? The most common reason is you created an app that doesn’t use the document architecture, and you want a way for your app’s users to access recent items.\n\nAdding an Open Recent menu requires you to do the following things:\n\n * Use the `NSDocumentController` class to track recent items.\n * Add an `@Observable` class to hold the recent items so SwiftUI can update the Open Recent menu when its contents change.\n\n\n\n## Using `NSDocumentController` to track recent items\n\nThe name `NSDocumentController` implies that it works with `NSDocument`. However, the parts of `NSDocumentController` that deal with the Open Recent menu require only an array of URLs. You can use `NSDocumentController` without creating a document app.\n\nUse the `shared` instance to access the standard document controller.\n\n\n NSDocumentController.shared\n\n\nUse the `recentDocumentURLs` property to access the URLs of the recent items and fill the Open Recents menu. Call the `noteNewRecentDocumentURL` function to update the recent items after opening or creating an item. Call the `clearRecentDocuments` function to clear the recent items list and clear the Open Recent menu.\n\n## Initial version of the Open Recent menu\n\nAn initial version of an Open Recent menu looks like the following code:\n\n\n struct OpenRecentMenu: View {\n @Environment(\\.openWindow) private var openWindow\n\n var body: some View {\n Menu(\"Open Recent\") {\n ForEach(NSDocumentController.shared.\n recentDocumentURLs, id: \\.self) { url in\n\n Button(url.lastPathComponent) {\n // Item is a class in your app.\n let itemToOpen = Item(location: url)\n openWindow(value: itemToOpen)\n NSDocumentController.shared.\n noteNewRecentDocumentURL(url)\n }\n }\n\n Divider()\n Button(\"Clear Menu\") {\n NSDocumentController.shared.\n clearRecentDocuments(nil)\n }\n }\n }\n }\n\n\nThe code creates the Open Recent menu and fills it with recent items. There is one big problem. The Open Recent menu doesn’t update until you restart the app.\n\nThe reason the menu doesn’t update is SwiftUI doesn’t automatically update the menu when the shared document controller’s array of recent items changes.\n\nTo get the menu to update, you must add an `@Observable` class that holds the recent item URLs and pass that class to the menu view.\n\n## Create the `@Observable` class\n\nThe class has two requirements. First, it needs a property that holds the URLs of the recent items. Second, it needs a function to update the array from the shared document controller.\n\n\n @Observable\n class RecentItems {\n @MainActor static let shared = RecentItems()\n var items: [URL] = []\n\n init(items: [URL]) {\n self.items = items\n }\n\n @MainActor func refresh() {\n let docControllerRecents = NSDocumentController.\n shared.recentDocumentURLs\n if docControllerRecents != items {\n items = docControllerRecents\n }\n }\n }\n\n\nSupply the value `NSDocumentController.shared.recentDocumentURLs` to the `RecentItems` initializer to fill the Open Recent menu when your app launches.\n\nWhen you open or add an item in your app, call the `refresh` function to update the Open Recent menu.\n\n## Add the `@Observable` class to the Menu\n\nTo use your `@Observable` class in the menu, you must add a `@State` property for the class to the App struct. Pass the value to the menu using a binding. Use the array in your `@Observable` class to fill the Open Recent menu.\n\nThe following code shows the updated Open Recent menu:\n\n\n struct OpenRecentMenu: View {\n @Environment(\\.openWindow) private var openWindow\n @Binding var recents: RecentItems\n\n var body: some View {\n Menu(\"Open Recent\") {\n ForEach(recents.items, id: \\.self) { url in\n Button(url.lastPathComponent) {\n let item = Item(location: url)\n openWindow(value: item)\n NSDocumentController.shared.\n noteNewRecentDocumentURL(url)\n recents.refresh()\n }\n }\n\n Divider()\n Button(\"Clear Menu\") {\n NSDocumentController.shared.\n clearRecentDocuments(nil)\n recents.refresh()\n }\n }\n }\n }\n",
"title": "Add an Open Recent Menu to a SwiftUI app"
}