How do you detect if an AppKit app has been launched with a document? - swift

I am building an NSDocument-based app and I want to be able to detect if my app is being launched because a user is opening a document, or because the user simply clicked on the dock icon. I tried inspecting the Notification that comes with the applicationDidFinishLaunching method, but it seems not to contain a reference to the document. I also tried querying NSDocumentController.shared.documents from this method, but the array is still empty at this point. I have noticed that by the time applicationDidBecomeActive is called, the document has been instantiated, but of course, this method is also called on occasions not related to app launch, so that's not ideal. My best guess at the moment is to do the following, but it feels like a hack.
class AppDelegate: NSObject, NSApplicationDelegate {
private var hasBecomeActiveOnce = false
func applicationDidBecomeActive(_ notification: Notification) {
if !hasBecomeActiveOnce {
hasBecomeActiveOnce = true
if !NSDocumentController.shared.documents.isEmpty {
print("App launched from document!")
}
}
}
}
There must be a better (more idiomatic) way. What am I missing?

Okay, I think I've found the answer to my specific problem, which might not be fully communicated by the question above. Basically, what I want to do is show an open panel when the app is launched, or when the dock icon is clicked and there isn't a document already open. It seems like in both of these cases, the system calls applicationOpenUntitledFile. Answering my own question in case others are running into this same issue.
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationOpenUntitledFile(_ sender: NSApplication) -> Bool {
print("app launched (or dock clicked) without a document!")
return true
}
}

Related

NSWindow "Edited" not shown

I have an application, which is primarily for presenting documents but can under certain circumstances also change the presented document.
That's why my app isn't a real document based app.
Nevertheless I want to display the "— Edited" additive to my window title, when the document has been edited and the changes weren't saved yet.
Therefore I have to methods in my AppDelegate
#objc func didEditDocument(_ notification: Notification) {
myMainWindow.windowController?.setDocumentEdited(true)
}
#objc func didSaveDocument(_ notification: Notification) {
myMainWindow.windowController?.setDocumentEdited(false)
}
I was expecting my window title to change from MyWindow to MyWindow — Edited after calling .setDocumentEdited(true), but that didn't happen. But the dot in the red close button changes. What am I doing wrong?
What am I doing wrong
Nothing. When you rejected the NSDocument architecture, you rejected the automatic "Edited" title change along with a lot of other automatic crunchy goodness. Nothing wrong with that, but then you can't complain when the crunchy goodness is missing. If you want the title changed, you'll have to change it yourself.

deinit not called on boilerplate macOS app

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 ?

Cocoa app: how to close a window without terminating the app?

I am working on a Cocoa app, using Swift. The app opens with a window and the user can open another window using the menu. The problem is that when either window is closed using the red button, the app terminates. I don't want that behavior; I want the app to remain alive. It's not crashing; the AppDelegate's applicationWillTerminate method executes.
The AppDelegate has the method applicationShouldTerminateAfterLastWindowClosed, which returns false, but this method never gets executed.
I am a reasonably competent Swift iOS developer; this is my first real Cocoa app and I suspect this is something simple, but I'm baffled right now. How do I close a window without terminating the app entirely?
Edit: Here are some code snippets:
In the AppDelegate:
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
Log.methodEnter()
return false
}
Also in the AppDelegate, the method to open a window:
#IBAction func openVerbSettingsWindow(sender: NSMenuItem) {
verbSettingsWindowController.showWindow (nil)
}
From the windowViewController, the only method that does anything as the window loads and displays:
override func windowDidLoad() {
super.windowDidLoad()
window?.setFrame(NSMakeRect(400.0, 100.0, 950.0, 450.0), display: true)
}
I'm hopeful this helps!
I'm not a little embarrassed at the answer to this question; it has nothing to do with Cocoa or my code.
I have an app called RedQuits that configures OS X to close an app when that app's last window closes, rather than leaving the app running with no windows (OS X default behavior). When I got rid of RedQuits and re-booted so that OS X would return to its default behavior, the problem went away. For some reason, RedQuits causes my Cocoa app to quit when any application window closes. Interestingly, I found this because it causes the same behavior in Xcode: close one project window and Xcode shut down.

How can I close a Safari App Extension popover programmatically?

I'm building a Safari App Extension using XCode 8.3 and Swift 3, following the Safari App Extension Programming Guide. The extension includes a popover that appears when the extension's toolbar item is clicked. The popover view contains a few buttons linked to actions the user can perform.
I want clicking one of these buttons to close the popover after its action has been performed. By default, clicking anywhere outside of a popover closes it, but I haven't been able to find any other way to close the popover, either in the guide or in the docs.
I know that NSPopover has a performClose method, but there doesn't appear to be a way to access the popover itself from within the extension: the app extension only lets you provide a SFSafariExtensionViewController, whose contents magically appear within the popover.
I've also tried using dismissViewController as described in this StackOverflow answer, but in my view controller self.presenting is always nil, and self.dismissViewController(self) just crashes the extension with the message:
dismissViewController:: Error: maybe this view controller was not presented?.
Lastly, I noticed a related question about programmatically opening the toolbar item popover has gone unanswered the past 6 months. This leads me to suspect Apple may simply have strict limits on how the popover can be opened and closed. Even if this is the case, it would be nice to know for sure what the limitations are.
I'll add an answer in case anyone stumbles upon this question.
A dissmissPopover() instance method has been added to the SFSafariExtensionViewController class. This can be used to programatically close the popover.
The default template given when creating a Safari App Extension in XCode gives you a SafariExtensionViewController class that extends SFSafariExtensionViewController and holds a shared instance as a static field called 'shared', so you can call the dismissPopover() method from that instance.
For example:
class SafariExtensionHandler: SFSafariExtensionHandler {
func myFunc() {
// do stuff;
SafariExtensionViewController.shared.dismissPopover()
// do other stuff;
}
}
I did it by calling dismiss method like below
#IBAction func onLoginBtnClicked (_ sender: Any) {
NSLog("Button clicked")
self.dismiss(self)
}

Refresh Today Widget every time opened

I thought that Today View every time when i open it it calls "viewWillAppear" but its not. When i change something in my app, and then I slide down for Today View it sometimes refresh the view and sometimes not.
I do all logic in viewWillAppear (fetch data from coreData and put that data to labels), but its not called everytime.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
fetchContent()
setLabels()
setContentHeight()
tableView.reloadData()
print("view will appear")
}
how to call fetchContent and setLabels every time when user opens Today Extensions?
For this matter you should be using NSWidgetProvinding's widgetPerformUpdateWithCompletionHandler.
Steps:
1.- Make sure that your UIViewController implements NCWidgetProviding
class MainViewController: UIViewController, NCWidgetProviding
2.- Add the following function:
func widgetPerformUpdateWithCompletionHandler(completionHandler: ((NCUpdateResult) -> Void)) {
// Perform any setup necessary in order to update the view.
// If an error is encountered, use NCUpdateResult.Failed
// If there's no update required, use NCUpdateResult.NoData
// If there's an update, use NCUpdateResult.NewData
completionHandler(NCUpdateResult.NewData)
}
3.- In your case you will be using .NewData.
Just make sure that you retrieve needed data and refresh your views (putting every data in place, filling labels, graphs, etc).
Nevermind the fact that your view is not visible during the call to this function, iOS will fill the view and take a snapshot of it.
Then that's what it shows while you are opening notification center and until you gain control again of your app.
So, in your case would be something like this:
func widgetPerformUpdateWithCompletionHandler(completionHandler: ((NCUpdateResult) -> Void)) {
fetchContent()
setLabels()
setContentHeight()
tableView.reloadData()
completionHandler(NCUpdateResult.NewData)
}
Swift 2.1 && Xcode 7.2
It looks like some bug appears when you many time recompile this today extension. solution is to remove from notification center and add it again. then it refresh fine everytime opened