Apple Documentation led me to this video that I watched: https://developer.apple.com/videos/play/wwdc2019/212
Which made me consider implementing this function: requestSceneSessionRefresh(_
I thought that, if I implemented this function by moving the ItemStore and Navigation settings away from the scene() function, it would refresh the content of the window in the background and pick up changes from the foreground once I switch windows:
(SceneDelegate.swift)
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow window
to the provided UIWindowScene scene
.
// If using a storyboard, the window
property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see application:configurationForConnectingSceneSession
instead).
guard let _ = (scene as? UIWindowScene) else { return }
// Gold Challenge: moved to its own function???
self.requestSceneSessionRefresh(session)
}
...
// for Gold Challenge
func requestSceneSessionRefresh(_ session: UISceneSession) {
// moving from scene(_ scene: willConnectTo session: options connectionOptions:)
// Create an ItemStore
let itemStore = ItemStore()
// listing 12.1 p 250 - updating the SceneDelegate to consider UINavigationController
let navController = window!.rootViewController as! UINavigationController
let itemsController = navController.topViewController as! ItemsViewController
itemsController.itemStore = itemStore
// Gold Challenge - does this do anything by being here?
}
…
This change didn’t make a difference. I still have two separate windows with their own sets of Items.
Am I implementing the right function?
You don’t need to implement that function, it’s already implemented for you as part of UIApplication.
Okay. If I don’t need to implement that method, do I need to modify any of the other optional functions in SceneDelegate.swift to refresh a listing once I switch windows?
And just so I’m clear on what the challenge is asking for: do they want…
a. the same new items to appear in multiple windows, regardless of their original listing?
or
b. entirely identical item lists within each window, without closing the window to reload the Items?
I only made changes to the SceneDelegate function that was already being modified for LootLogger. However, the changes I made to that function related to refreshing the window weren’t strictly necessary, I made them in order to eliminate what I thought were some unnecessary display updates. I think the app would function adequately without them. The only necessary changes I made to SceneDelegate were related to the ItemStore.
I don’t understand the difference between the two options you listed. My goal was to have a change in one window reflected in the other windows without having to bring them to the foreground. So in split view, both views should update at the same time, and if there are any other copies running in the background their windows should look up to date in Show All Windows. This should include adding, deleting, and moving items as well as editing the name, price or serial number.
I didn’t change the way the details view functioned overall, though - the contents of that view are still set when it is first displayed and saved when the view is exited. So that causes inconsistencies if the details view is displayed for the same item in multiple windows. I though about making additional changes to deal with that, but never got to it.
I tried grafting the scene function from Apple’s example:
// for Gold Challenge
if let userActivity = connectionOptions.userActivities.first ?? session.stateRestorationActivity {
let navController = window!.rootViewController as! UINavigationController
let itemsController = navController.topViewController as! ItemsViewController
itemsController.itemStore = itemStore
} else {
Swift.debugPrint("Failed to restore from \(userActivity)")
}
But the app crashed as a result, pointing to my ItemsViewController.
"Failed to restore from nil"
Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value: file ItemsViewController.swift, line 73
You suggested that you only made changes to the scene() function in SceneDelegate.swift. I read the Apple Documentation on this function and couldn’t find anything extensive on maintaining a single data source for multiple windows of an app.
My searches on Google and StackOverflow for “ios swift single data source for multiple app windows” don’t provide recommendations I can apply (the latter actually gives me 0 results).
I’m lost.
In an app with multiple window support, each window has its own scene and its own scene delegate. So if you create ItemStore in SceneDelegate each scene has its own store, which is not what you want. You need to move the creation of ItemStore out of SceneDelegate and into AppDelegate, so there’s just one ItemStore for the application shared across all scenes.
I tried this, but still no dice:
AppDelegate.swift:
class AppDelegate: UIResponder, UIApplicationDelegate {
// Gold Challenge -- single data source for all windows to share
let itemStore = ItemStore()
// ...but then what?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
// Gold challenge - why isn't there an AppDelegate equivalent of
// 'let itemsController = navController.topViewController as! ItemsViewController'
// so I can set it = itemStore?
return true
}
...
[rest of AppDelegate.swift]
…
SceneDelegate.swift:
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
let appDelegate = AppDelegate() // for Gold Challenge?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
guard let _ = (scene as? UIWindowScene) else { return }
// listing 12.1 p 250 - updating the SceneDelegate to consider UINavigationController
let navController = window!.rootViewController as! UINavigationController
let itemsController = navController.topViewController as! ItemsViewController
itemsController.itemStore = appDelegate.itemStore
// Gold challenge - why isn't this grabbing the itemStore from AppDelegate as my shared data source?
}
...
[rest of SceneDelegate.swift]
You have the right idea that SceneDelegate should get a reference to itemStore from AppDelegate & pass it down to the view controller, but you don’t need to add code to create an AppDelegate from within SceneDelegate. Your application is already creating an AppDelegate, and SceneDelegate already has a reference to it (though it’s not easy to find, it’s buried inside another structure).
As for why you can’t access the view controller from AppDelegate, I don’t know for certain that you can’t, but in this case I don’t think you want to. Each time you add another window you’re creating another ItemViewController that needs to have its itemStore reference set. In order to do that from AppDelegate it would have to have a function that gets called each time a new scene is created, and I don’t know that there is such a function in AppDelegate. It’s easier to just let SceneDelegate do it, I think that’s the sort of thing it’s supposed to do.
Alright, I got it to work:
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
let appDelegate = UIApplication.shared.delegate as! AppDelegate // for Gold Challenge
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
guard let _ = (scene as? UIWindowScene) else { return }
// Gold Challenge initialize the ItemStore for the Scene
let itemStore = appDelegate.itemStore
let navController = window!.rootViewController as! UINavigationController
let itemsController = navController.topViewController as! ItemsViewController
itemsController.itemStore = itemStore
// Gold challenge - grabs the appDelegate's itemStore
}