This seems like a newbie question but I've been struggling with it for a while now. I have some code initialisation that I'd like to run before my app launches.
At the moment I have set up my AppDelegate as follows:
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate
{
func applicationDidFinishLaunching(aNotification: NSNotification)
{
runMyInitCode()
}
}
My problem is that all my object code runs before this. Where is the best place to call initialisation methods before anything else runs?
You have 4 options (in order of appearance):
Override init()
Override awakeFromNib()
Delegate notification applicationWillFinishLaunching
Delegate notification applicationDidFinishLaunching
The best place depends on your needs.
If IBOutlets are involved put it not before awakeFromNib.
If you are using a view based table view don't put it in awakeFromNib because it could be called multiple times.
Registering NSUserDefaults can be put everywhere.
Consider also lazy initialization.
Consider also Cocoa Bindings.
Related
I am wondering if there is any way that can solve the ViewModel executing before the AppDelegate in my macOS app.
Suppose I have these files:
AppDelegate.swift
final class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
UserDefaults.shared.register(defaults: [ // <--- breakpoint 1
"userDefaultItem1" : false,
"userDefaultItem2" : 0
])
}
}
AppName.swift
#main
struct AppName: App {
#NSApplicationDelegateAdaptor(AppDelegate.self)
private var appDelegate // <--- breakpoint 2
#StateObject
var vm = ViewModel() // <--- breakpoint 3
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(vm)
}
}
}
When I run the application, it hits breakpoint 2, then breakpoint 3, then breakpoint 1.
As a result, none of the UserDefaults are registered prior to access in the ViewModel and it crashes.
Is this something that is supposed to happen, and though the use of AppDelegates are not required, there is use in legacy code that is now moved over to SwiftUI.
if you want to make sure that something is processed before even AppDelegate gets its delegate methods called then you could also place UserDefaults code in init of your AppDelegate. Or also possible, simply move the code over to applicationWillFinishLaunching which is called earlier.
init() { .. } will work because NSObjects always follow the alloc-init pattern. So init gets called first (second after alloc) before even any delegate method is called.
Also when your App crashes when UserDefaults are missing, then it is worth testing what happens when there are no UserDefaults used at all. This will force you to code a fallback and then you apply UserDefaults onto those prepared fallback properties "refreshing" them and go on from there. That makes sure when a User opens your app the first time, where most likely there are no UserDefaults set, it will not crash.
In short: using UserDefaults without fallback is a design pattern issue. You should always provide evaluation of UserDefaults before applying and sometime even before changing them, because Users can even erase them or otherwise manipulate them on a mac.
PS: your ViewModel is called very likely via an entry in Storyboard or main nib and is accordingly marked in Info.plist to do so.
another point to think about is, when this particular UserDefault is needed for the ViewModel then it is also worth to recall and evaluate them in init() of the ViewModel instead of AppDelegate
I've created new app from macOS Cocoa App template. The only change I've made is added deinit in NSViewController. So now it looks like this(complete code):
import Cocoa
class ViewController: NSViewController {
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 doSomething()
{
var a = 10
a = a + 1
print(a)
}
deinit {
doSomething()
print("deinit called")
}
}
Why I don't see deinit call? I've searched number of questions here, but couldn't find answer, as I don't have any retain cycle.
As Tobi says in his answer, deinit gets called right before an object is deallocated.
An object gets deallocated when there are no longer any strong references to it. (Nobody owns the object any more.)
In order to answer your specific question you need to look at how your view controller is created an who owns it.
I haven't done much Mac development in a while, so I'm kinda rusty on view controller lifecycle, but here's my recollection of how it works:
If you only have one app window, it is the view controller's owner, and the window never gets closed, that means the view controller will never get deallocated.
If you quit the app I don't think the system tears down your window hierarchy before terminating the app (unless you're app is a document-based app, in which case the app will be told to close all of it's document windows before quitting.)
A deinitializer is called immediately before a class instance is
deallocated.
Altho your question is not clear to me.
But the usual reason for failure to trigger deinit when expected is that you have a retain cycle that prevents your view controller from going out of existence.
Sometimes the reason is that your expectation that the view controller would be destroyed under the circumstances is incorrect.
But assuming it is correct, a retain cycle is the reason.
another suggestion is to use Hierarchies Debug.
answer those questions for your self.
is this the root UIViewController ?
is it dismissed properly ?
I have an NSWindowController that shows a table and uses another controller as the data source and delegate for an NSTableView. This second controller displays information from an object, which is passed in by the NSWindowController. That controller in turns has the object set as a property by the AppDelegate. It looks like this:
class SomeWindowController: NSWindowController {
var relevantThing: Thing!
var someTableController: SomeTableController!
#IBOutlet weak var someTable: NSTableView!
override func windowDidLoad() {
someTableController = SomeTableController(thing: relevantThing)
someTable.dataSource = someTableController
someTable.delegate = someTableController
}
}
In the AppDelegate I then do something like
func applicationDidFinishLaunching(_ aNotification: Notification) {
relevantThing = Thing()
someWindowController = SomeWindowController()
someWindowController.relevantThing = relevantThing
someWindowController.showWindow(nil)
}
Is this a reasonable approach? I feel like the implicitly unwrapped optionals used in SomeWindowController might be bad form. Also, relevantThing is not allowed to change in my case, so I feel a let would be more correct. Maybe the relevantThing should be made constant and passed in through the initializers? Or would that break the init?(coder: NSCoder) initializer?
I'd greatly appreciate any suggestions, as I'm trying to get a feel for the right way to do things in Swift.
A few things:
Is there any reason your are creating your window controller in code and not loading it from a storyboard/xib?
Generally, a better practice is to put all your 'controller' that relates to a view in a NSViewController and use NSWindowController only for stuff that relates to the window itself (e.g. toolbar, window management, etc).
Similarly to iOS, NSViewController is now integrated into the window/view lifecycle and responder chain. For many windows you don't even need to subclass NSWindowController.
XCode's app project template creates a storyboard with the window, main view and their controllers. This is a good starting point.
NSWindowController has a contentViewController property that is set to the NSViewController of the main content view (when loaded from storyboard). You generally don't need a separate view controller property for your view controller.
I think that usually, you want to minimize modifying your controllers from outside code and make them as independent as possible. This makes them more testable and reusable.
If your Thing instance is global for the entire application (as it appears from your code), you may want to consider adding it as a singleton instance to the Thing class and retrieving it from the NSViewController (e.g in viewDidLoad())
If you put your controllers/views in storyboard, you can connect the table's datasource/delegate there. And if this your main window, it can load and show it automatically when the app starts. But in any case, put your NSViewController/View wiring in the view controller.
If you want to separate logic between your main NSViewController into a more specialized view controller that handles a specific part of your view, you can use NSContainerView in Interface Builder to add additional view controllers to handle specific views.
In one of my Watch Extension's interface controllers I have several WKInterfacePicker elements, and I need to know when the user has selected a value. According to documentation, WKInterfaceController should be able to implement pickerDidSettle(_:) method that has the corresponding picker element as parameter. For some reason the method never gets called when I use the pickers. Here is the basic structure of my implementation:
override func pickerDidSettle(picker: WKInterfacePicker) {
// Code inside this block is not called
}
If I mark the function with an #IBAction attribute and connect them with the picker elements in interface builder, the instance method works. However, this apparently prevents me to assign picker actions that receive all the picker values through which the user is scrolling.
#IBAction
override func pickerDidSettle(picker: WKInterfacePicker) {
// This function gets called, but blocks other actions
}
My interface controller inherits from WKInterfaceController and conforms to two custom protocols. How should I implement the method?
Edit: The issue was related to a possible bug in WatchKit, where pickerDidSettle(_:) will not be called without an existing #IBAction connection to the controller. I assume it is a bug, because related instance methods pickerDidFocus(_:) and pickerDidResignFocus(_:) work independent of the connection.
Sometimes this issue occurs, when something gets 'out of sync' between Xcode and the Simulator.
Just close the Simulator and clean and rebuild your app (via 'Product/Clean Build Folder') to recreate the 'sync.
I am looking at Apple documentation here and not able to implement it in my swift project. I want to set a UIButton as the first focused in View when the app starts but cant figure out how.
I am primarily looking to do something like this:
override func preferredFocusedView .... but obviously that is not it
It's a property, not a function, so you need to do this in Swift:
override weak var preferredFocusedView: UIView? {
get {
return viewYouWantToFocus
}
}
In your UIViewController override get{} for preferredFocusedView to return your UIButton. Then make sure to call setNeedsFocusUpdate(). That should do it. (You can also implement set{} so that you can easily change which object receives focus, but again, remember to call setNeedsFocusUpdate() after changing it).