I'm a complete beginner to Swift, so this may be a silly question, but I can't figure out how this works...
I have a view with a button inside which calls the following code:
let window = NSWindow()
window.center()
window.title = "test"
window.makeKeyAndOrderFront(self)
When I click the button the window opens just for a moment and disappears a few milliseconds later.
Can anyone help me with that? It seems I have a quite serious misunderstanding about views in Cocoa ;-)
Thanks
Tom
The problem is that you are creating and 'storing' the NSWindow in your button action function. That means that as soon as that button action is done, the NSWindow will go out of context, and be released and thus disappear.
This is how the memory management in Swift works: as soon as nobody owns an object anymore, it will be released.
What you should do is put your window in an instance variable. Like for example:
class YourViewController: NSViewController {
private var window: NSWindow!
#IBAction func buttonAction(sender: UIButton) {
window = NSWindow()
window.center()
window.title = "test"
window.makeKeyAndOrderFront(self)
}
}
The hint about makeKeyAndOrderFront(nil) makes no difference. Passing either nil or self is fine. But latter, how you did it originaly, makes more sense.
Related
I have a non-document based macOS AppKit app. It has one window instantiated automatically by the storyboard. I have sub-classed NSWindowController and added a override func newWindowForTab(_ sender: Any?) to enable the + button on the tab-bar. My main view controller lets the user rename the tab title and the window title is set to the same. This is kind of like how Xcode tab renaming works.
Additionally I have sub-classed NSWindow and added a restorableStateKeyPaths to ensure tab and window titles are automatically restored on app restart.
This all works great.
But only for the first tab. The main window is loaded and it has the tab and window titles set automatically.
The other tabs (windows) are not restored.
Any hints on what I miss to make all tabs restored?
My NSWindowController:
class MyWindowController: NSWindowController {
var subview: MyWindowController?
#IBAction override func newWindowForTab(_ sender: Any?) {
let story = self.storyboard
let windowVC = story?.instantiateInitialController() as! Self
window?.addTabbedWindow(windowVC.window!, ordered: .above)
subview = windowVC
windowVC.window?.orderFront(self.window)
windowVC.window?.makeKey()
}
}
My NSWindow:
class MyWindow: NSWindow {
override class var restorableStateKeyPaths: [String] {
return [ "self.tab.title", "self.title" ]
}
}
First you need to make sure that state restoration is enabled for your user, you can do this by going to Preferences->General and unchecking "Close windows when quitting an app".
Then you should use a restoration class in order to restore all open windows.
Basically if an NSWindow doesn't have a restoration class it won't be preserved across launches, that includes your storyboard loaded window. In this case what is happening is Cocoa is ignoring all window preservation because you haven't defined a restoration class for any of your windows so it resorts to its default behavior which is loading the initial storyboard controller.
Implementing restoration class is easy, just create a restoration class that inherits from NSObject and conforms to NSWindowRestoration, then implement its only required type method restoreWindow(identifier:state:completionHandler) like so:
class MyAppWindowRestoration: NSObject, NSWindowRestoration {
static func restoreWindow(withIdentifier identifier: NSUserInterfaceItemIdentifier,
state: NSCoder,
completionHandler: #escaping (NSWindow?, Error?) -> Void) {
// 1.- Retrieve and show the window
// Retrieve a new instance of the only window
let window = (NSStoryboard.main?.instantiateInitialController() as? NSWindowController)?.window
// Call the completion handler with the window and no errors
completionHandler(window, nil)
}
}
Then just assign this class as the window restoration class on every window you want restored, you can do this everywhere after the window has loaded:
window.restorationClass = MyAppWindowRestoration.self
Unfortunately Apple's documentation on state restoration completely sucks so if you have any more questions let me know ;)
I'm trying to find how to bring up a second view/window after pushing a button on my primary window. I have read about segues and I can get the first window to display the second but the second is not connected to a view controller so I can't add any code to any controls on the second view. Try as I might I cannot create a SecondViewController.swift file and connect it to a window controller or a view controller. The tutorials I have found all deal with iOS and I want OS X which means there are just enough differences to keep me from figuring this out.
Can anyone show me how to do this?
Ta,
A.
First make new file like:
After that, put these codes in your classes and that should do it.
class SecondWindowController: NSWindowController {
convenience init() {
self.init(windowNibName: "SecondWindowController")
}
}
class ViewController: NSViewController {
private var secondWindowController: SecondWindowController?
#IBAction func showSecondWindow(sender: AnyObject) {
if secondWindowController == nil {
secondWindowController = SecondWindowController()
}
secondWindowController?.showWindow(self)
}
}
How to handle close event of the window using swift, for example, to ask "Are you sure you want to close the form?"
The form will be closed in the case "yes" and not closed in the case "no". Showing message box is not a problem for me.
viewWillDisappear() works for minimizing also, but I need only close event.
Thanks.
Like said above, you should make the ViewController an NSWindowDelegate, but you should handle windowWillClose, not windowShouldClose. windowShouldClose is to determine if the window is able to close or not, not an event that the window is actually closing.
I also found that you need to set up the delegate in viewDidAppear, not viewDidLoad. For me self.view.window wasn't defined yet in viewDidLoad.
override func viewDidAppear() {
self.view.window?.delegate = self
}
I was having the same query too, solved it using the method explained in detail here: Quit Cocoa App when Window Close using XCode Swift 3
It needs three steps:
Conform toNSWindowDelegate in your ViewController class
Override viewDidAppear method
Add windowShouldClose method
The added code should look like this:
class ViewController: NSViewController, NSWindowDelegate {
// ... rest of the code goes here
override func viewDidAppear() {
self.view.window?.delegate = self
}
func windowShouldClose(_ sender: Any) {
NSApplication.shared().terminate(self)
}
}
You can use the NSWindowDelegate protocol in your ViewController class. (See the documentation here)
To make your class conform to the protocol:
class ViewController: NSObject, NSWindowDelegate
To detect when the window's close button has been clicked, use windowShouldClose:
From the doc:
Tells the delegate that the user has attempted to close a window [...]
In this method, you can use NSAlert to prompt the user on whether or not they really want to close the window.
EDIT (in response to #Mr Beardsley's comment)
To make your ViewController the delegate, use:
window.delegate = self
Where self is the ViewController and window is the window you're using. You can put this in viewDidLoad:.
Just add this function to AppDelegate ...
func applicationShouldTerminateAfterLastWindowClosed (_ theApplication: NSApplication) -> Bool {
return true
}
Does someone knows why in ElCapitan GM and Xcode 7 GM the popover appears outside of the view?
The popover is triggered by the "Button".
The picture below is a new project with no code written by me, jut a button.
Is it a bug or a new "feature"?
I just tried it, and it seems that you can't set the popover anchor in the Storyboard. Perhaps this is indeed a bug in the new release.
To display the popover programmatically, set the StoryboardID of your popover view controller, for example: "PopoverViewController". Below, it's implemented in the main view controller as a lazy var, so it's instantiated just once, the first time it's referenced.
Connect an IBAction from your button to the main view controller — here, a function called "displayPopover". The "guard let" statement makes sure the sender can be cast as an NSButton.
Then, just call:
presentViewController:asPopover...
lazy var popoverViewController: NSViewController = {
return self.storyboard!.instantiateControllerWithIdentifier("PopoverViewController")
as! NSViewController
}()
#IBAction func displayPopover(sender: AnyObject) {
guard let button = sender as? NSButton else {return}
self.presentViewController(popoverViewController, asPopoverRelativeToRect: button.frame, ofView: button, preferredEdge: NSRectEdge.MaxY, behavior: NSPopoverBehavior.Transient)
}
I am simply trying to get undo working for the actions a user performs in my app. By default, any text editing the user does has the benefit of undo, but any actions that are done otherwise (from my code) does not.
I can see the documentation explains that I need to get an instance of NSUndoManager and call registerUndoWithTarget, but I am stumped with the first step: getting the undoManager from within my ViewController. Since ViewController is a UIResponder, I tried this:
if let undoManager = self.undoManager {
undoManager.registerUndoWithTarget(self, selector: Selector("removeLatestEntry:"), object: "test")
}
Since that binding returns nil, I thought maybe the ViewController doesn't have the undoManager, so I looked for it in the window:
if let window = NSApplication.sharedApplication().mainWindow {
if let undoManager = window.undoManager {
undoManager.registerUndoWithTarget(self, selector: Selector("removeLatestEntry:"), object: "test")
}
}
Alas, the window binding also returns nil. Sorry, I am very new to this. Can anyone point me in the right direction? Am I supposed to implement my own undoManager or something? There is clearly an undoManager somewhere because anything a user does manually in my textField is getting undo behavior. It seems like this would be a singleton that I could access easily from a ViewController.
--
Edit: BTW, the code above was placed in viewDidLoad and removeLatestEntry is just a function in my ViewController that takes a string and prints it at this point.
To use undoManager, the ViewController needs to be first responder. So:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
becomeFirstResponder()
}
Then, from wherever your action is defined that needs to be reversed, you register your undo and pass it whatever it needs to undo that action. So in my case:
func addEntry(activity: String) {
// some other stuff…
undoManager!.registerUndoWithTarget(self, selector: Selector("removeLatestEntry:"), object: activity)
}