Setting Realm schema version in MacOS app - swift

Unlike iOS, the app delegate's applicationWillFinishLaunching and applicationDidFinishLaunching are called after ViewDidLoad on the initial NSViewController.
Main.c (or main.swift) seems to be retired and without changing AppDelegate too much, the only place I can think of to call schemaVersion is in the ViewDidLoad of the initial view controller, which seems ugly to me.
What is recommended way to set Realm's schemaVersion in MacOS apps written in Swift?

The init() of the MacOS AppDelegate seems to be working great.
class AppDelegate: NSObject, NSApplicationDelegate {
override init() {
super.init()
let info = Bundle.main.infoDictionary!
let version = info["RealmSchemaVersion"] as! UInt64
Realm.Configuration.defaultConfiguration.schemaVersion = version
}
...
}

Related

Firebase exception error for macOS app Xcode

When I try to run the simulator for my macOS app, which is using Firebase, it gives this error: "Thread 1: "The default FirebaseApp instance must be configured before the default Authinstance can be initialized. One way to ensure this is to call FirebaseApp.configure() in the App Delegate's application(_:didFinishLaunchingWithOptions:) (or the #main struct's initializer in SwiftUI)." I notice this happened after I created an environment object.
Here is my code:
import SwiftUI
import FirebaseCore
#main
struct testagainApp: App {
#NSApplicationDelegateAdaptor(AppDelegate.self) var delegate
var body: some Scene {
WindowGroup {
let viewModel = AppViewModel()
ContentView()
.environmentObject(viewModel)
}
.windowStyle(HiddenTitleBarWindowStyle())
}
}
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
FirebaseApp.configure()
}
}
If I get rid of let viewModel = AppViewModel() and .environmentObject(viewModel), the simulator runs just fine. If I put the app delegate first, the simulator runs but nothing appears. I am new to Swift and am unsure how to fix this.
Option 1:
I faced same issue a while ago, then noticed that I have not linked the
GoogleService-Info.plist file to the project, if you see its already added then better remove and add it to the project via Xcode again.
Hope that solves your issue.
Option 2:
Make sure you don't have more two copies of Firebase linked into your app. If so you can remove one. See this doc on the Firebase iOS SDK for more details.
I solved it. Instead of using the AppDelegate I used the App's initializer:
struct testagainApp: App {
init() {
FirebaseApp.configure()
}
// ...
}

Swift Command Line Tool Not Receiving DistributedNotificationCenter Notifications

I am trying to create a very basic Swift command-line application that signals to another application using a WebSocket when the macOS UI changes to/from light/dark mode.
For some reason, the command-line tool is not receiving any notifications from DistributedNotificationCenter, in particular, AppleInterfaceThemeChangedNotification. However, running the exact same code in a Cocoa UI app on applicationDidFinishLaunching works perfectly fine.
I found an old Obj-C CLI project on Github that is meant to print out every notification, but that doesn't do anything either. It makes me suspect Apple perhaps changed something, but I cannot seem to find anything online about it. Are there certain Xcode project settings I need to set?
// main.swift
import Foundation
class DarkModeObserver {
func observe() {
print("Observing")
DistributedNotificationCenter.default.addObserver(
forName: Notification.Name("AppleInterfaceThemeChangedNotification"),
object: nil,
queue: nil,
using: self.interfaceModeChanged(notification:)
)
}
func interfaceModeChanged(notification: Notification) {
print("Notification", notification)
}
}
let observer = DarkModeObserver.init()
observer.observe()
RunLoop.main.run()
I managed to get iTunes notifications working, so it was just the theme change notifications that weren't working. Given this, I suspect Apple only sends the notifications to UI/NSApplication applications. As such, replacing the last 3 lines from above with the following works:
let app = NSApplication.shared
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
let observer = DarkModeObserver.init()
observer.observe()
}
}
let delegate = AppDelegate()
app.delegate = delegate
app.run()

Is there a main.swift which is equivalent to the #NSApplicationMain annotation?

Creating a new Cocoa project in XCode gives me an AppDelegate.swift file which looks like this:
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
}
The #NSApplicationMain attribute is documented here as
NSApplicationMain
Apply this attribute to a class to indicate that it is the application delegate. Using this attribute is equivalent to calling the NSApplicationMain(_:_:) function.
If you do not use this attribute, supply a main.swift file with code at the top level that calls the NSApplicationMain(_:_:) function as follows:
import AppKit
NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
The instructions in the documentation do not work: the AppDelegate class is never instantiated. In this answer, vadian suggests the following contents for main.swift, which work better than the code in the documentation:
import Cocoa
let appDelegate = AppDelegate()
NSApplication.shared().delegate = appDelegate
_ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
However, this still does not provide the same behavior as #NSApplicationMain. Consider using the above main.swift with the following AppDelegate.swift:
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
var foo: NSStatusBar! = NSStatusBar.system();
}
The above AppDelegate.swift works with an #NSApplicationMain annotation, but when using the above main.swift, it fails at runtime with the error
Assertion failed: (CGAtomicGet(&is_initialized)), function CGSConnectionByID, file Services/Connection/CGSConnection.c, line 127.
I think this is_initialized error means that #NSApplicationMain sets things up so that the AppDelegate is instantiated after some initialization by the NSApplicationMain function. This suggests the following main.swift, which moves the delegate initialization to after the NSApplicationMain call:
import Cocoa
_ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
let appDelegate = AppDelegate()
NSApplication.shared().delegate = appDelegate
However, this doesn't work either, because NSApplicationMain never returns! The above main.swift is equivalent to the broken suggestion in the documentation, because the latter two lines are dead code.
I therefore think there must be some way to pass a reference to my AppDelegate class as an argument to the NSApplicationMain function, so that Cocoa can do its initialization and then instantiate my AppDelegate class itself. However, I see no way to do this.
Is there a main.swift which provides behavior which is truly equivalent to the #NSApplicationMain annotation? If so, what does that main.swift look like? If not, what is #NSApplicationMain actually doing, and how do I modify it?
The documentation assumes that there is a xib or storyboard which instantiates the AppDelegate class via an object (blue cube) in Interface Builder. In this case both
main.swift containing NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
and
#NSApplicationMain in the AppDelegate class
behave exactly the same.
If there is no xib or storyboard you are responsible to initialize the AppDelegate class, assign it to NSApplication.shared.delegate and run the app. You have also to consider the order of appearance of the objects. For example you cannot initialize objects related to AppKit before calling NSApplication.shared to launch the app.
For example with this slightly changed syntax
let app = NSApplication.shared
let appDelegate = AppDelegate()
app.delegate = appDelegate
_ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
you can initialize the status bar in AppDelegate outside ofapplicationDidFinishLaunching:
let statusItem = NSStatusBar.system().statusItem(withLength: -1)
because NSApplication.shared() to launch the app is called before initializing the AppDelegate class.
Here is what I did in order to run application without #NSApplicationMain annotation and function NSApplicationMain(_, _) while using Storyboard with initial NSWindowController generated by Xcode application template (with slight modification related to Main Menu described below).
File: AppConfig.swift (Swift 4)
struct AppConfig {
static var applicationClass: NSApplication.Type {
guard let principalClassName = Bundle.main.infoDictionary?["NSPrincipalClass"] as? String else {
fatalError("Seems like `NSPrincipalClass` is missed in `Info.plist` file.")
}
guard let principalClass = NSClassFromString(principalClassName) as? NSApplication.Type else {
fatalError("Unable to create `NSApplication` class for `\(principalClassName)`")
}
return principalClass
}
static var mainStoryboard: NSStoryboard {
guard let mainStoryboardName = Bundle.main.infoDictionary?["NSMainStoryboardFile"] as? String else {
fatalError("Seems like `NSMainStoryboardFile` is missed in `Info.plist` file.")
}
let storyboard = NSStoryboard(name: NSStoryboard.Name(mainStoryboardName), bundle: Bundle.main)
return storyboard
}
static var mainMenu: NSNib {
guard let nib = NSNib(nibNamed: NSNib.Name("MainMenu"), bundle: Bundle.main) else {
fatalError("Resource `MainMenu.xib` is not found in the bundle `\(Bundle.main.bundlePath)`")
}
return nib
}
static var mainWindowController: NSWindowController {
guard let wc = mainStoryboard.instantiateInitialController() as? NSWindowController else {
fatalError("Initial controller is not `NSWindowController` in storyboard `\(mainStoryboard)`")
}
return wc
}
}
File main.swift (Swift 4)
// Making NSApplication instance from `NSPrincipalClass` defined in `Info.plist`
let app = AppConfig.applicationClass.shared
// Configuring application as a regular (appearing in Dock and possibly having UI)
app.setActivationPolicy(.regular)
// Loading application menu from `MainMenu.xib` file.
// This will also assign property `NSApplication.mainMenu`.
AppConfig.mainMenu.instantiate(withOwner: app, topLevelObjects: nil)
// Loading initial window controller from `NSMainStoryboardFile` defined in `Info.plist`.
// Initial window accessible via property NSWindowController.window
let windowController = AppConfig.mainWindowController
windowController.window?.makeKeyAndOrderFront(nil)
app.activate(ignoringOtherApps: true)
app.run()
Note regarding MainMenu.xib file:
Xcode application template creates storyboard with Application Scene which contains Main Menu. At the moment seems there is no way programmatically load Main Menu from Application Scene. But there is Xcode file template Main Menu, which creates MainMenu.xib file, which we can load programmatically.
Replace the default Cocoa project's AppDelegate.swift with the following main.swift. The application will behave the same as before. Thus, the following code provides the semantics of the #NSApplicationMain annotation.
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate { }
let myApp: NSApplication = NSApplication.shared()
let myDelegate: AppDelegate = AppDelegate()
myApp.delegate = myDelegate
let mainBundle: Bundle = Bundle.main
let mainNibFileBaseName: String = mainBundle.infoDictionary!["NSMainNibFile"] as! String
mainBundle.loadNibNamed(mainNibFileBaseName, owner: myApp, topLevelObjects: nil)
_ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
(I constructed this with much help from vadian's answer. If there are any differences in behavior between the above and the default Cocoa project application, please let me know.)

ViewController + Storyboard setting up validation with controlTextDidChange

Trying to setup validation for a few text fields in a new (and very small) Swift Mac app. Following various other topics here on SO and a few other examples, I can still not get controlTextDidChange to propagate (to my ViewController).
E.g: How to live check a NSTextField - Swift OS X
I have read at least a dozen variations of basically that same concept. Since none of the accepted answers seem to work I am just getting more and more confused by something which is generally a fairly simple task on most platforms.
I have controlTextDidChange implemented to just call NSLog to let me know if I get anything.
AppDelegate should be part of the responder chain and should eventually handle controlTextDidChange but I see nothing there either.
Using the current Xcode I start a new project. Cocoa app, Swift, Storyboard and nothing else.
From what I can gather the below isolated example should work. In my actual app I have tried some ways of inserting the ViewController into the responder chain. Some answers I found suggested it was not always there. I also tried manually adding the ViewController as the delegate in code theTextField.delegate = self
Nothing I have done seems to get text changed to trigger any events.
Any ideas why I have so much trouble setting up this delegation?
My single textfield example app
Storyboard is about as simple as it gets:
AppDelegate
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate, NSTextFieldDelegate, NSTextDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
func controlTextDidChange(notification: NSNotification) {
let object = notification.object as! NSTextField
NSLog("AppDelegate::controlTextDidChange")
NSLog("field contains: \(object.stringValue)")
}
}
ViewController
import Cocoa
class ViewController: NSViewController, NSTextFieldDelegate, NSTextDelegate {
#IBOutlet var theTextField: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
func controlTextDidChange(notification: NSNotification) {
let object = notification.object as! NSTextField
NSLog("ViewController::controlTextDidChange")
NSLog("field contains: \(object.stringValue)")
}
}
I think the samples you're following are a bit out-of-date.
Try...
override func controlTextDidChange(_ notification: Notification) {
...as the function definition for your method in your NSTextFieldDelegate.

Sending replyToApplicationShouldTerminate to NSApplication in Swift

Here is my AppDelegate.swift. I implement the applicationShouldTerminate protocol from NSApplication. Which answer I give depends on the status of is.Started in the mainWindowController. (This is the SpeakLine example from Cocoa Programming for OS X: The Big Nerd Ranch Guide 5/e—I'm trying to take the example one step further and keep the program from being allowed to quit while the talking is going on.)
What I want to do is change TerminateReply.TerminateCancel to TerminateReply.TermianteLater and then send NSApplication the replyToApplicationShouldTerminate(true) signal when the talking is done. As it stands now in the MainControllerWindow.swift class I have a function set up to handle state changes in the Speech Synthesizer and that's where I want to call it.
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var mainWindowController: MainWindowController?
func applicationDidFinishLaunching(aNotification: NSNotification) {
let mainWindowController = MainWindowController()
mainWindowController.showWindow(self)
self.mainWindowController = mainWindowController
}
func applicationShouldTerminate(sender: NSApplication) -> NSApplicationTerminateReply {
if (mainWindowController!.isStarted) {
return NSApplicationTerminateReply.TerminateCancel
} else {
return NSApplicationTerminateReply.TerminateNow
}
}
}
The trouble is, when I put it here, I get an error.
var isStarted: Bool = false {
didSet {
updateButtons()
NSApplication.replyToApplicationShouldTerminate(true)
}
}
it tells me I can't use a bool. It also tells me I can't use an Objective C bool when I try to put YES. How do I tell NSApplication it's OK to quit now?
I believe you should change
NSApplication.replyToApplicationShouldTerminate(true)
to
NSApplication.sharedApplication().replyToApplicationShouldTerminate(true)
since replyToApplicationShouldTerminate is a instance method rather then a class method.
my tow cents for swift 5.x:
NSApplication.shared.reply(toApplicationShouldTerminate: true)