New toolbar appearance in macOS 11

I’ve a project that is in development and not yet
released so I can adopt new tech without having to worry about existing releases and users. It’s a Core Data app, originally targeted iOS.

I’m writing a macOS target using as much SwiftUI as possible.

I understand - maybe incorrectly - I cannot adopt the new Window in SwiftUI because I must use AppDelegate for my Core Data stack - or do I?

Maybe this is a part of the problem?

I’d like to build a new toolbar for my macOS target that matches the styling of toolbars already used throughout macOS Bog Sur. I’m struggling to figure out how I implement this larger toolbar and add Toolbar items. I’ve watched the talk on What’s New In SwiftUI and attempted to drop some of the toolbar sample code into my project but, while it compiles, the code is ineffective.

any help please?

Accepted Reply

Hi andrewbuilder,

Although many of Xcode's app templates perform Core Data setup in the App Delegate, it's OK to set up your Core Data stack anywhere you'd like. So, if you want to adopt the new app/scene support in SwiftUI, one option is to set up a NSPersistentContainer in a custom class that you write, and then your SwiftUI App could hold on to an instance of that class in a variable. The SwiftUI Data Essentials session might provide some inspiration on organizing your data and passing it through to your views.

On the other hand, if your app is still using a Storyboard and/or NSWindowController to host your SwiftUI content, you won't be able to set up the toolbar in SwiftUI, but you're free to configure the toolbar directly in AppKit. The 'Adopt the new look of macOS' session outlines some of the AppKit API that you'd use to customize your toolbar for the new design.

Replies

Hi andrewbuilder,

Although many of Xcode's app templates perform Core Data setup in the App Delegate, it's OK to set up your Core Data stack anywhere you'd like. So, if you want to adopt the new app/scene support in SwiftUI, one option is to set up a NSPersistentContainer in a custom class that you write, and then your SwiftUI App could hold on to an instance of that class in a variable. The SwiftUI Data Essentials session might provide some inspiration on organizing your data and passing it through to your views.

On the other hand, if your app is still using a Storyboard and/or NSWindowController to host your SwiftUI content, you won't be able to set up the toolbar in SwiftUI, but you're free to configure the toolbar directly in AppKit. The 'Adopt the new look of macOS' session outlines some of the AppKit API that you'd use to customize your toolbar for the new design.
So for any passers-by that are interested in how I eventually achieved this...

(Credit to mtsrodrigues for how to deal with change of Scene in his answer in this thread... https://developer.apple.com/forums/thread/650876)

My Core Data implementation for SwiftUI and the new App and Scene protocols and Window container!

My SwiftUI App...

Code Block
struct MyApp: App {
    @Environment(\.scenePhase) private var scenePhase
    @StateObject private var persistentStore = PersistentStore.shared
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.managedObjectContext, persistentStore.context)
        }
        .onChange(of: scenePhase) { phase in
            switch phase {
            case .active:
                print("\(#function) REPORTS - App change of scenePhase to ACTIVE")
            case .inactive:
                print("\(#function) REPORTS - App change of scenePhase to INACTIVE")
            case .background:
                print("\(#function) REPORTS - App change of scenePhase to BACKGROUND")
                savePersistentStore()
            @unknown default:
                fatalError("\(#function) REPORTS - fatal error in switch statement for .onChange modifier")
            }
        }
    }
    func savePersistentStore() {
        persistentStore.save()
    }
}


and my custom class for PersistentStore (noting the one line singleton as documented by KrakenDev)...

Code Block
import SwiftUI
import CoreData
class PersistentStore: ObservableObject {
    var context: NSManagedObjectContext { persistentContainer.viewContext }
// One line singleton
    static let shared = PersistentStore()
// Mark the class private so that it is only accessible through the singleton `shared` static property
    private init() {}
    private let persistentStoreName: String = "YourPersistentStoreName"
    // MARK: - Core Data stack
    lazy var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: persistentStoreName)
        // Enable history tracking
        // (to facilitate previous NSPersistentCloudKitContainer to load as NSPersistentContainer)
        // (not required when using NSPersistentCloudKitContainer)
        guard let persistentStoreDescriptions = container.persistentStoreDescriptions.first else {
            fatalError("\(#function): Failed to retrieve a persistent store description.")
        }
        persistentStoreDescriptions.setOption(true as NSNumber,
                                              forKey: NSPersistentHistoryTrackingKey)
        persistentStoreDescriptions.setOption(true as NSNumber,
                                              forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error {
                fatalError("Unresolved error \(error)")
            }
        })
        container.viewContext.automaticallyMergesChangesFromParent = true
// Add the following line to set your merge policy...
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
        return container
    }()
   
    // MARK: - Core Data Saving and Undo support
    func save() {
        let context = persistentContainer.viewContext
        if !context.commitEditing() {
            NSLog("\(NSStringFromClass(type(of: self))) unable to commit editing before saving")
        }
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                let nserror = error as NSError
                NSApplication.shared.presentError(nserror)
            }
        }
    }
}