Use NSPanel for user input. Not opening up again - swift

I want to display a 'NSPanel' for the user to input a name for a new folder. Why a NSPanel? Because it looks awesome! It hosts one TextField and one PushButton to confirm the name. It shall also close the window when clicked.
It displays when the "add" button gets clicked in my menu. It also closes when the "done" button gets clicked in the NSPanel. But when I click "add" again it doesn't show up anymore. That also occurs when I close it via the normal "close button" in the title bar. So it is not explicitly related to the "done"-PushButton. I also tested implementing func windowWillClose(notification: NSNotification) which also doesn't get triggered in either cases. What could be the problem? Also, does it somehow need to be a "new" window every time? Or am I using this correctly for user input? I mean it just gets instantiated once and then "shown" and "unshown" or am I wrong?
So I did a new Cocoa-Class - Subclass of NSWindowController - and let xCode create a .xib for that also. In that .xib I "designed" the NSPanel. I ticked visible at launch without that the window wouldn't appear when the menu button gets clicked. I also hooked up an IBOutlet for the NSPanelin my Cocoa Class. My Class at the moment looks like this:
import Cocoa
class NamingHUD: NSWindowController, NSWindowDelegate {
#IBOutlet var insertNameWindow: NSPanel!
#IBOutlet weak var nameTextField: NSTextField!
override var windowNibName : String! {
return "NamingHUD"
}
override func windowDidLoad() {
super.windowDidLoad()
insertNameWindow.center()
insertNameWindow.makeKeyAndOrderFront(nil)
NSApp.activateIgnoringOtherApps(true)
}
#IBAction func userSetName(sender: NSButton) {
print("Close button clicked")
insertNameWindow.close()
}
}
In my Main Class I declared it as a variable like this:
var namingHUD:NamingHUD!
and then in override func awakeFromNib() as:
namingHUD = NamingHUD()
as well as in a click handler like:
#IBAction func addClicked(sender: NSMenuItem) {
namingHUD.showWindow(nil)
}
Now. When I click and addClicked() gets called the window shows up as expected. Fine! I enter a name and hit the "done" button and it closes the window properly. Also Fine! But when I click again, say to add another folder, the window doesn't show up anymore. I also created a Preferences Window the exact same way. But with a Window instead of a NSPanel inside. That totally works as it should.
So I clearly confused something or forget something. What could it be? I openly admit that it is the first time I am working with any kind of window outside of following a tutorial. So I clearly didn't grasp the whole concept of it. I read up about windows in Apples Developer Guide and it kinda makes sense. But... well, doesn't work at the moment. Am I "misusing" the NSPanel? Shouldn't be the case as it inherits from NSWindow or?

Did you connect the window outlet of NamingHUD to your awesome panel? Nibs are loaded lazily:
namingHUD = NamingHUD() // init the controller but doesn't load the nib
...
namingHUD.showWindow(nil) // now you are loading it for the first time
It works the first time because showWindow() loads the nib and show the window referenced by the window outlet. Your panel shows up because it's set to "Visible at launch". Your of course had no window to show.
Subsequent clicks don't load the nib file again, only order the window outlet to show up. That's why your panel did not show again. FYI: an NSPanel is a subclass of NSWindow so it has everything that NSWindow has, and then some more.

Related

How to get multiple windows to perform the same menu item action?

I have a Cocoa app which supports multiple windows.
I already know how to get the main window to perform actions for any of the NSMenuItem from NSMenu and including the NSToolbarItem from the NSToolbar.
class MainWindowController: NSWindowController {
// ...
#IBAction func doSomethingIncredible(_ sender: Any?) {
// ...
}
}
That is easy for a single window app but my app supports multiple windows.
But how can I get other windows apart from the main window to access and even validate the NSMenuItem from NSMenu?
An example of this is Safari. It supports multiple windows. You can select Open Location...(⌘L), New Tab (⌘T), Show Sidebar (⇧⌘L) etc. It does the action on the focused window. It is not tied to the main window. It can be done on any windows. How can I do this for my app?
class AnotherWindowController: NSWindowController {
// ...
#IBAction func doSomethingIncredible(_ sender: Any?) {
// ...
}
}
How can the MainWindowController and the AnotherWindowController perform the same action from one of the NSMenuItem from NSMenu when one of them is the key focused window?
Rather than hard-connecting the menu item to a specific controller connect it to the First Responder (the red cube).
The first object in the responder chain which responds to the seclector – usually the frontmost window – catches the action and executes it.

How to override Copy and Paste NSMenuItems for one View Controller Swift macOS

I am writing a macOS application with multiple view controllers, using Storyboards.
In my main View Controller I would like to be able to copy and paste data to the NSPasteboard. The data is related to buttons displayed to the user, and the exact data to be copied varies depending on which button has most recently been pressed/selected.
I would like to be able to override the standard behaviour of the Copy and Paste NSMenuItems when my main View Controller is the front most (key) window, but revert to back to standard behaviour when other windows are in the foreground, as they all contain NSTextFields which can be copied/pasted into.
I have done a lot of googling, and overriding this behaviour is not very well documented. I can achieve it globally by adding an IBAction into the App Delegate, which I could use to call a function in whichever View Controller is key, but this doesn't feel like a very elegant solution.
Currently my IBAction in the App Delegate looks like this:
#IBAction func copy(_ sender: Any) {
if let window = NSApplication.shared.keyWindow {
if let splitView = window.contentViewController as? SplitViewController {
if let controlVC = splitView.controlItem.viewController as? ControlViewController {
controlVC.copyAction(self)
}
}
}
}
Am I missing a neater solution?
Thanks,
Dan

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)
}

Xcode 7.2.1 Making a Reset Button using a Button with Swift

I am trying to make a reset button for my app that will reset the UI to the original state. I have made a UIButton and linked it to the ViewController, but I have no idea where to go from here. I tried using the following code:
#IBAction func resetToOriginalState(sender: UIButton) {
self.resetToOriginalState (sender: UIButton)
}
It gave me the following error:
Editor placeholder in source file
Sorry if there may be an obvious answer, but I am very new to Swift and Xcode.
Is there any other way to create a reset button?
The error:
Editor placeholder in source file
Is because you are calling a function with the UIButton Class name instead of the actual button.
#IBAction func resetToOriginalState(sender: UIButton {
// this line is wrong, you shouldn't have UIButton in here
self.resetToOriginalState (sender: UIButton)
// the line should read
self.resetToOriginalState (sender: sender)
}
This way, you are passing the actual button into the function that was passed to resetToOriginalState
Seems you have too IBAction for the same button, check how many time you have #IBAction func resetToOriginalState(sender: UIButton) in your code and remove the references from the references Interface list to clean it, should there be only one :
It depends what is in the scene and what do you need to reload. As far is I know you can't really segue a ViewController to itself, but here are few options:
Try to add loadView() when the button is pressed
Duplicate the view controller, and segue between the two. (might be risky and create more work)
Reset your variables to their initial state when the button is pressed
You should give us more detail because this is implementation specific.
Nevertheless, it's not very clean, but depending on the architecture of your code, you might be able to generate a new instance of your view controller, destroy the current one, and present the new one.

Start a main window using swift

I'm kind of new in swift, but I'm 'stuck' in something.
I know C, Java, C++, PHP, etc...
I'm trying to compose an multi-windowed App (Mac OS application using Cocoa), using swift. I can already do it, but my problem is when I close the main window. If I close my main window, when I try to add a child to it, I will get an error or the new window just does not open.
I have this:
class SmallWindow: NSViewController {
#IBOutlet var window: NSWindow! //Window inside the .xib file
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
}
}
I already tried the above using NSWindowController and NSWindow.
var smallwin = SmallWindow()
smallwin.window.makeKeyAndOrderFront(self)
//already tried with smallwin.window too
I am wondering how to programmatically start a new window. Am I missing some steps?
#IBOutlet var window: NSWindow!
#IBOutlet var smallwin : SmallWindow!
func applicationDidFinishLaunching(aNotification: NSNotification) {
smallwin = SmallWindow()
window.addChildWindow(smallwin.window, ordered: NSWindowOrderingMode.Above)
}
You probably don't need a child window. You just need a window.
To put a new window on the screen, you need to do three things:
Create the window, either programmatically or by loading a nib.
Send an order-in message to the window. Usually you want to send makeKeyAndOrderFront or orderFront. See “Opening and Closing Windows” in the Window Programming Guide.
Keep a strong reference to the window. The system doesn't necessarily keep a strong reference to an on-screen window. If the window is deallocated, it will remove itself from the screen.