Proper way to reuse NSWindowControllers - macos

This is a follow-up to this question.
I'm trying to create an app with a few windows that pop up on start,
and can be closed ,then opened again later.
I'm not having issues with reopening the windows anymore,
but issues with memory. It keeps going up, everytime I request those windows to show again.
Here is my code:
class AppDelegate: NSObject, NSApplicationDelegate {
var parentWC: NSWindowController?
var childWC: NSWindowController?
func showThem() {
if self.parentWC == nil {
let parentWindow = CustomWindow()
self.parentWC.window = parentWindow
}
if self.childWC == nil {
let childWindow = NSWindow()
self.childWC.window = childWindow
self.parentWC.window.addChildWindow(self.childWC.window)
}
}
func closeAllWindows() {
self.childWC.window.close()
self.parentWC.window.close()
}
// From app menu
#IBAction func showThemAgain(sender: AnyObject) {
self.parentWindow.window = nil
self.parentWC = nil
self.childWindow.window = nil
self.childWC = nil
self.showThem()
}
}
class CustomWindow: NSWindow {
func onCloseRequest() {
let appD = (NSApplication.sharedApplication().delegate) as! AppDelegate
appD.closeAllWindows()
}
}
If I'm closing the windows and settings the vars to nil, what is causing the memory usage to go up every time I show these windows? Are they not being closed?
*Note that there is a button in CustomWindow that is used for closing.

Related

Losing reference to NSWindows in AppDelegate

I'm running into issues where I lose reference to NSWindows that are declared in AppDelegate, with the code below.
class AppDelegate: NSObject, NSApplicationDelegate {
var window1: CustomWindow1!
var window2: CustomWindow2!
func setupWindows() {
self.window1 = CustomWindow1()
self.window2 = CustomWindow2()
let vc2 = VIEWOFSOMEVIEWCONTROLLER()
self.window2.contentView.addSubview(vc2.view)
self.window1.grandchildVC = vc2
self.window1.addChildWindow(self.window2!, ordered: NSWindowOrderingMode.Above)
// etc.
}
#IBAction addWindowsAgain(sender: AnyObject) {
// This is where if fails
if self.window1 != nil {
self.window1 == nil
}
}
}
class CustomWindow1() {
var grandchildVC: NSViewController
func deleteChildWindowThenSelf() {
self.grandchildVC.view.window.close()
self.close()
}
}
I am able to call deleteChildWindowThenSelf(), and get rid of the windows as expected, but when calling addWindowsAgain in AppDelegate, if fails with lldb error. Does AppDelegate lose those window vars when close is called, or am I misunderstanding something?
NSWindow has a variable releasedWhenClosed which is true by default for custom created windows unless the window is owned by a window controller.
Set the variable to false.

Conditionally showing NSViewController at app launch

I'm developing an OSX app where I show first a login/register window if the user hasn't logged in yet.
After login success I show my main view controller.
If the user is already logged in (a token is stored), then the app has to launch directly with the main view controller.
I'm new to OSX development, I googled for this kind of scenario but couldn't find anything.
So I went up with what I think should work. It works sometimes, sometimes I get a blank window.
In the storyboard I let the Main Menu and the Window Controller. I removed the "contains" segue to my main view controller.
In AppDelegate, I put this:
func applicationDidFinishLaunching(aNotification: NSNotification) {
if loggedIn {
self.showViewController(NSStoryboard.mainViewController())
} else {
let loginController = NSStoryboard.loginViewController()
loginController.delegate = self
self.showViewController(loginController)
}
}
private func showViewController(viewController: NSViewController) {
if let mainWindow = NSApplication.sharedApplication().mainWindow {
mainWindow.contentViewController = viewController
} else {
print("Error: No main window!")
}
}
About half of the times the window is empty and I see in the console "Error: No main window!". I thought maybe I can use applicationDidBecomeActive but this is called basically when it comes to the foreground and this is not what I need.
Further, the times when it works, and I log in, then I want to show the main view controller:
func onLoginSuccess() {
self.showViewController(NSStoryboard.mainViewController())
}
And here I also get "Error: No main window!" (always) and nothing happens.
The docs say following about mainWindow being nil:
The value in this property is nil when the app’s storyboard or nib file has not yet finished loading. It might also be nil when the app is inactive or hidden.
But why is the storyboard not finished loading or the app inactive when I'm launching it? And on login success the app is definitely active and in the foreground and the main window is always nil.
What am I doing wrong? How can I implement this workflow? Alternatively I could create a "parent" view controller, have that one connected to the window in the storyboard, and add the login or main view controller as nested view controllers to that. But don't really like having to add a do nothing view controller.
I'm using XCode 7(beta 4), Swift 2, OSX 10.10.4
Edit:
The NSStoryboard methods come from an extension, it looks like this:
extension NSStoryboard {
private class func mainStoryboard() -> NSStoryboard { return NSStoryboard(name: "Main", bundle: NSBundle.mainBundle()) }
private class func signupStoryboard() -> NSStoryboard { return NSStoryboard(name: "LoginRegister", bundle: NSBundle.mainBundle()) }
class func mainViewController() -> ViewController {
return self.mainStoryboard().instantiateControllerWithIdentifier("MainViewController") as! ViewController
}
class func loginViewController() -> LoginViewController {
return self.signupStoryboard().instantiateControllerWithIdentifier("LoginViewController") as! LoginViewController
}
class func registerViewController() -> RegisterViewController {
return self.signupStoryboard().instantiateControllerWithIdentifier("RegisterViewController") as! RegisterViewController
}
}
To put the solution we found in the comments as an answer:
Apparently NSApplication.sharedApplication().mainWindow is a different window than my main window in the storyboard.
So, I created an NSWindowController subclass and assigned it to the window in the storyboard, using the identity inspector.
Then I moved the logic I had in app delegate to this NSWindowController. It looks like this:
class MainWindowController: NSWindowController, LoginDelegate {
override func windowDidLoad() {
if loggedIn {
self.onLoggedIn()
} else {
let loginController = NSStoryboard.loginViewController()
loginController.delegate = self
self.contentViewController = loginController
}
}
func onLoggedIn() {
self.contentViewController = NSStoryboard.mainViewController()
}
func onLoginSuccess() {
self.onLoggedIn()
}
}
* Thanks Lucas Derraugh for pointing me in the right direction!
enum Storyboards: String {
case main = "Main"
case settings = "Settings"
func instantiateVC<T>(_ identifier: T.Type) -> T? {
let storyboard = NSStoryboard(name: rawValue, bundle: nil)
guard let viewcontroller = storyboard.instantiateController(withIdentifier: String(describing: identifier)) as? T else { return nil}
return viewcontroller
}
}
//Need to use like this
//Make sure Storyboard Id and class-name are the same
if let windowController = Storyboards.main.instantiateVC(IDMainController.self) {
windowController.showWindow(nil)
//----- OR -----
self.contentViewController = windowController
} else {
print("Cannot find IDMainController")
}

GameCenter not working, authentication and leaderboard not showing

With Swift 2, GameCenter is not working for me. The authentication ViewController is not showing up... Here is my func authenticateLocalPlayer():
func authenticateLocalPlayer() {
var localPlayer = GKLocalPlayer()
localPlayer.authenticateHandler = {(viewController: UIViewController?, error: NSError?) -> Void in
if (viewController != nil) {
self.presentViewController(viewController!, animated: true, completion: nil)
print("Not Authenticated. ")
} else {
print("Authenticated. ")
}
}
}
It is returning "Not Authenticated" every time, but is not presenting the ViewController. Any solution?
This solution presents the viewController correctly using Swift 2 in Xcode 7.0.
Note that I have changed the code before the if statement begins. I believe the syntax may have changed in a recent software update as I had this problem too.
In my app I called authenticateLocalPlayer() in the viewDidLoad() method of the GameViewController class.
func authenticateLocalPlayer() {
let localPlayer = GKLocalPlayer.localPlayer()
localPlayer.authenticateHandler = {(viewController, error) -> Void in
if (viewController != nil) {
self.presentViewController(viewController!, animated: true, completion: nil)
}
else {
print((GKLocalPlayer.localPlayer().authenticated))
}
}
}

Label is nil in custom controller class

// This is in ViewController.swift
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var oneLabel: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
oneLabel.stringValue = "All is well" // Here it works
//...
}
}
// Separate Swift code file
import Cocoa
import Foundation
var si = Simulate()
class Simulate: NSViewController {
#IBOutlet weak var aLabel: NSTextField!
func simulationManager() -> Bool {
var ni: Int
var breakPoint = false
rd.simStatus = .Running
do {
if rd.rchIndex >= ld.NodeCount(.Reach) {
if InterStepConvergence() {
NextTimeStep()
if aLabel != nil { // This is always false
aLabel.stringValue = String(rd.elapsedSec)
}
else {
println("Label is nil")
}
//...
}
}
}
}
}
I am trying to set up a custom controller to update the interface while a
simulation is running. I need to show the status of the simulation. The simulation runs in a separate thread, but even if I do it in the main thread, same problem as described below.
The label text can be changed if I do it in the ViewController class as above.
But if I try to modify the text on the label in the Simulate class the label
is always nil and so it doesn't work. But the code compiles OK. What am I missing here such that the label is always nil in the Simulate class? Thanks much, in advance.

How to use NSWindowOcclusionState.Visible in Swift

I am trying to implement window toggling (something I've done many times in Objective-C), but now in Swift. It seams that I am getting the use of NSWindowOcclusionState.Visible incorrectly, but I really cannot see my problem. Only the line w.makeKeyAndOrderFront(self) is called after the initial window creation.
Any suggestions?
var fileArchiveListWindow: NSWindow? = nil
#IBAction func tougleFileArchiveList(sender: NSMenuItem) {
if let w = fileArchiveListWindow {
if w.occlusionState == NSWindowOcclusionState.Visible {
w.orderOut(self)
}
else {
w.makeKeyAndOrderFront(self)
}
}
else {
let sb = NSStoryboard(name: "FileArchiveOverview",bundle: nil)
let controller: FileArchiveOverviewWindowController = sb?.instantiateControllerWithIdentifier("FileArchiveOverviewController") as FileArchiveOverviewWindowController
fileArchiveListWindow = controller.window
fileArchiveListWindow?.makeKeyAndOrderFront(self)
}
}
Old question, but I just run into the same problem. Checking the occlusionState is done a bit differently in Swift using the AND binary operator:
if (window.occlusionState & NSWindowOcclusionState.Visible != nil) {
// visible
}
else {
// not visible
}
In recent SDKs, the NSWindowOcclusionState bitmask is imported into Swift as an OptionSet. You can use window.occlusionState.contains(.visible) to check if a window is visible or not (fully occluded).
Example:
observerToken = NotificationCenter.default.addObserver(forName: NSWindow.didChangeOcclusionStateNotification, object: window, queue: nil) { note in
let window = note.object as! NSWindow
if window.occlusionState.contains(.visible) {
// window at least partially visible, resume power-hungry calculations
} else {
// window completely occluded, throttle down timers, CPU, etc.
}
}

Resources