How to add Command-Q logic in Cocoa with Swift? - cocoa

I want to add some logic in my Cocoa app when an user puts Command-Q to try to quit the app, but how can I add it in Swift?
What I want to do is something like this, but this is on Objective-C and when I tried to port it to my Swift app, it doesn't succeed since it looks like applicationShouldTerminate is no longer available in Swift, given that it doesn't react to the completion of the method in my Xcode.
Also, when I tried to write NSTerminateNow like the one written on the post above, I got an unresolved error, so NSTerminateNow is also gone in Swift and Xcode 6.
So if I want to add some logic after an user puts Command-Q in my Swift and Cocoa app, how can I implement it in Xcode 6? Should I avoid using applicationShouldTerminate method?

The same method works in Swift without problems:
class AppDelegate: NSObject, NSApplicationDelegate {
// ...
func applicationShouldTerminate(sender: NSApplication!) -> NSApplicationTerminateReply {
let shouldTerminate = ... // Should I stay or should I go?
return shouldTerminate ? .TerminateNow : .TerminateCancel
}
}

It's still there, it's just hiding. If you command click on NSApplicationDelegate in your app delegate's declaration, you can see all the methods it implements, including:
func applicationShouldTerminate(sender: NSApplication!) -> NSApplicationTerminateReply {
// stuff and return stuff
}
It seems that (hopefully just for now) optional methods declared in protocols, aren't suggested by autocomplete.

Related

keyDown(with:) Not Called on Custom ViewController or View, Only WindowController

I have moved most of the core functionality of my non-document based macOS app to a custom, embedded framework.
The app code has a standard main storyboard with an initial window, and the window has a "window content" relationship/segue into a storyboard reference pointing to a storyboard inside the embedded framework. Therein lies a custom NSViewController subclass and a custom NSView subclass.
I want to group all the input event handling code inside the framework, which means implementing mouseDown(with:) on the custom NSView subclass, and --lo and behold-- it gets called when I click inside the app window. So far, so good.
Next, I implemented keyDown(with:) to similarly handle keyboard input. However, at runtime, it does not get called and instead, I hear the annoying beep (NSBeep).
I tried implementing keyDown(with:) on the view controller instead, but it's all the same.
Finally, I tried implementing the key handler on my NSWindowController subclass instead, and that does work.
So I could get around this by forwarding the event like so:
class WindowController: NSWindowController {
override func keyDown(with event: NSEvent) {
contentViewController?.view.keyDown(with: event)
}
}
, but it is very inelegant. I would prefer to not pollute the app code with input logic.
This doesn't seem to have anything to do with embedding frameworks, however. I put together a minimal project from the 'Cocoa App' template and confirmed that indeed keyDown(with:) only gets called if implemented on the window controller code, but not on the view or view controller side.
How can I get keyDown(with:) to be called on the view or view controller (not the window or window controller) in a storyboard-based app? (so I can move it from the main app to my embedded framework).
Edit: The question has been marked as duplicate. I tried the solutions pointed in answers to the other question (namely, override acceptsFirstResponder to return true). This solves the problem in my minimal demo project, but when I tried it on my full app, it still does not work (I did see that question and did try to override acceptsFirstResponder in my app before posting this question).
I will now try to modify my minimal poeject to see if I can reporduce the issue in the main app.
Edit 2: I have refactored the minimal project to:
Send the view controller to a separate storyboard,
Send the view controller's storyboard and represented classes (custom view, custom view controller) to a separate, embedded framework.
Now the basic setup mirrors that of my app in all that seems to matter, but still can not reproduce the issue in the minimal project. I will investiate further...
Edit 3: I haven't been able to reproduce the issue on the minimal project.
On my app's custom view, I implemented:
public override var acceptsFirstResponder: Bool {
return true
}
public override func performKeyEquivalent(with event: NSEvent) -> Bool {
let retVal = super.performKeyEquivalent(with: event)
return retVal
}
On startup, acceptsFirstResponder is called twice.
When hitting any key, performKeyEquivalent(with:) is called twice, too. Inspectig the intermediate variable retVal above reveals that the super class's implementation always returns false. After returning from this method, NSBeep() is called and keyDown(with:) isn't.
If instead of super.performKeyEquivalent(with:) I force-return true, I can avert the call to NSBeep() (but keyDown(with:) is still not called...)
Edit 4 (Final):
Out of desperation, I cleared the "Custom Class" field of the window controller's Identity Inspector in my app's main storyboard (to the default NSWindowController).
Suddenly, my custom view's keyDown(with:) starts getting called.
I reinstated the custom class to confirm.
It still works.
I clean the build folder and try again.
It still works.
Now I can no longer reproduce the issue even on my main app. I really don't know what to say...

WKWebView process terminates without sandbox entitlement

I'm trying to build a simple MacOS app with a web view. I'm using WKWebView and for no obvious reason the webViewWebContentProcessDidTerminate() method is called immediately after the web view is initialized without the web view even trying to load anything. After hours of looking in the documentation and in issues online, I cannot figure out why this is happening and how to prevent it. The only thing suggested online was to reload the web view when this happens which results in the method getting called again a moment later and so on endlessly. I'm now down to a completely empty MacOS app with the following source code:
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
var webView: WKWebView!
func applicationDidFinishLaunching(_ aNotification: Notification) {
webView = WKWebView(frame: .zero, configuration: WKWebViewConfiguration())
webView.navigationDelegate = self
}
}
extension AppDelegate: WKNavigationDelegate {
func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
print("web view process did terminate")
webView.reload()
}
}
It doesn't matter if the web view is in the view hierarchy, has a non-zero frame and generally if it is visible or not. I've also enabled arbitrary loads just for the sake of trying out everything.
I would really appreciate some help on this. I'm compiling against 10.13 SDK and running on a MacOS 10.12.
Thanks.
I managed to figure it out. For anybody else struggling with this, you need to add the com.apple.security.network.client entitlement to your app.
If wkwebview is getting terminated, you can add reload method inside webViewWebContentProcessDidTerminate method, which basically reloads the webview.
Check the below code,
file name: CRAWKWebView.m
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView
{
RCTLogWarn(#"Webview Process Terminated");
if (webView) {
[webView reload];
}
}
reload instance is provided by wkwebview.

Game Center not authenticating using Swift

I'm trying to authenticate the local player using swift, but every time I get a false value for the .authenticated property. Here is the code I'm using, it is called by the main view controller when the app starts.
func authenticateLocalPlayer(){
var localPlayer = GKLocalPlayer()
localPlayer.authenticateHandler = {(viewController, error) -> Void in
if viewController {
self.presentViewController(viewController, animated: true, completion: nil)
}else{
println((GKLocalPlayer().authenticated))
}
}
}
It brings up the log in view just fine, but when I enter a test account login, it just returns the GKLocalPlayer().authenticatedas false. The bundle identifier in iTunes Connect and the info.plist are exactly the same, as is the version and the app name. Everything is enabled for Game Center on iTunes Connect and in Xcode, but I have a feeling it's not a coding error, it's a setup error in the app record somewhere but I can't for the life of me find where.
After further tinkering, I'm getting this error:
Error Domain=GKErrorDomain Code=15 "The requested operation could not be completed because this application is not recognized by Game Center." UserInfo=0x17006b300 {NSLocalizedDescription=The requested operation could not be completed because this application is not recognized by Game Center.}
I have no idea why this is the case, the bundle ID, name and versions all match...
Any help would be greatly appreciated.
This issue has been resolved by Apple - just call:
GKLocalPlayer.localPlayer()
Previously, the issue was that GKLocalPlayer() does not return the GKLocalPlayer singleton, but instead returns a new GKLocalPlayer instance.
If you were on the Xcode 6 BETA, you could add a C function or Objective-C method that returns the real GKLocalPlayer singleton, then use this in Swift. This is the gist of my workaround (with bad naming conventions):
In an Objective-C header:
GKLocalPlayer *getLocalPlayer(void);
In an Objective-C implementation:
GKLocalPlayer *getLocalPlayer(void) {
return [GKLocalPlayer localPlayer];
}
In your bridging header:
#import "ThatHeader.h"
Then whenever you need to access the GKLocalPlayer singleton in Swift, you can just use getLocalPlayer() instead of GKLocalPlayer(). It's probably a better idea to stick that in an method of a GKLocalPlayer category.
However, this is no longer necessary as detailed above.
Even with Xcode 6 Beta 6, on a device using iOS 8 beta 5, making GKLocalPlayer.localPlayer() available, I was still getting the error:
"NSLocalizedDescription=The requested operation could not be completed
because this application is not recognized by Game Centre"
The solution (discovered through Apple's Dev forum) was to go to "Settings" on the device, and then into "Game Centre" and enable "Sandbox" under the developer section.
You can use that, I create a simple class for iOS game center in GitHub
Easy Class Game Center Swift
https://github.com/DaRkD0G/Easy-Game-Center-Swift

User interface is not visible when the app runs (only in interface builder)

I'm stuck on a very stupid issue. I've built my mac osx app user interface with Interface Builder (and xcode3).
Now when I run my app I can't see the app window (but only the menu on top).
The MyDocument.xib file is correctly loaded (from xCode navigation sidebar) and I can see my user interface in interface builder.
In my code I haven't changed this method:
- (NSString *)windowNibName
{
// Override returning the nib file name of the document
// If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this method and override -makeWindowControllers instead.
return #"MyDocument";
}
What am I doing wrong ?
thanks
Check out the following methods in the NSApplicationDelegate documentation:
(BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender
(BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag
Each window has an option (checkbox) called visible at start (or something like that; can't check ATM). Doublecheck if that's activated.
It was a bad configured ArrayController!
No error messages.. just the interface not showing up

Qutting a cocoa app after closing a window

I have a small osx cocoa app that brings up an IKPictureTaker at start up, I would like for my application to quit after this picture taker is closed. I read that I need to add this code to my NSWindowController class but I have no idea how to access this class (it shows up no where in my class list in XCode):`
-(BOOL) applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication
{
return YES;
}
You need to create a custom object that implements the NSApplicationDelegate protocol and implement the applicationShouldTerminateAfterLastWindowClosed method there.
If you already have an application delegate (you more than likely do), just add it there.
You have to add this method to your application delegate, whatever object that is.

Resources