windowWillClose and button action not called Swift - swift

I'm designing a mac app with Xcode 10 (beta) and I got an issue with the Preference Window Controller
I have in my Main.storyboard a NSWindowController of custom class PreferenceWindowController with a toolbar. Here are its connections :
Here is the full class :
class PreferenceWindowController: NSWindowController, NSWindowDelegate {
#IBAction func didClickAuthor(_ sender: Any) {
print("author")
}
#IBAction func didClickTypo(_ sender: Any) {
print("typo")
}
override func windowDidLoad() {
super.windowDidLoad()
}
func windowWillClose(_ notification: Notification) {
print("willClose")
}
}
The window is initiated via the AppDelegate class with this code :
let storyboard = NSStoryboard(name: "Main",bundle: nil)
if let wc = storyboard.instantiateController(withIdentifier: "PreferenceWindowController") as? PreferenceWindowController
{
wc.showWindow(self)
}
The window opens as expected, with the toolbar clickable, but no functions from PreferenceWindowController are called at all, neither the closing of the window, nor the clicks on the toolbar.
I checked every connections, every class name, and I really don't know what's wrong...
SOLUTION
The solution is to store the PreferenceViewController class inside the AppDelegate class as a variable.
My solution :
var preferenceWindowController:PreferenceWindowController? = nil
#IBAction func clickPreferences(_ sender: Any) {
if let wc = storyboard.instantiateController(withIdentifier: "PreferencesWindowController") as? PreferenceWindowController {
let window = wc.window
preferenceWindowController = wc
wc.showWindow(self)
}
}
Thank you for helping !

The comment above seems like it could be on the right track. Based on the code context you've included in your question, it looks like the window controller you create will only have a lifetime for that function call.
Try making the window controller an instance variable. This is normally how I wire things up in an App delegate that creates window controllers. It's a simple pattern that works well.

Related

Xcode: didTransition to Won't Run in iMessage Extension

I am making an iMessage extension that uses the didTransition(to:). However, the function won't run when I resize the iMessage extension in the simulator. Am I doing something wrong?
This is the code I have:
import UIKit
import Messages
class EditorViewController: MSMessagesAppViewController {
#IBOutlet weak var input: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
input.text = "not changed"
}
// This part isn't working:
override func didTransition(to presentationStyle: MSMessagesAppPresentationStyle) {
input.text = "changed"
}
}
When I resize it in the simulator, nothing happens. The input.text changes the UITextView's text in the viewDidLoad() function, but not in the didTransition(to) function because it never runs.
Am I doing something wrong?
The EditorViewController is a view controller presented by the show (e.g. Push) segue, and has a NavigationController attached to it.
Here is a gif of it not changing:
The input's text never changes
How can I fix this?
EDIT: The willTransition and didTransition functions don't run when the View Controller is embedded in a Navigation Controller. Is there a way to fix this? (It works without the Navigation Controller, but I need the Navigation Controller for this project).
As pointed out in this answer, the entry point of a iMessage App need to be a subclass of MSMessagesAppViewController, so you can not use a NavigationViewController directly as root controller, until Apple adds support for this behavior.
But as suggested, you could solve this with a workaround like this:
import UIKit
import Messages
class MyRootVC: MSMessagesAppViewController {
var navVC: UINavigationViewController!
var editorVC: EditorViewController!
func viewDidLoad() {
super.viewDidLoad()
editorVC = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController() as! EditorViewController
navVC = UINavigationController(rootViewController: editorVC)
self.addChild(navVC)
self.view.addSubview(navVC.view)
navVC.didMove(toParent: self)
}
override func didTransition(to presentationStyle: MSMessagesAppPresentationStyle) {
editorVC.input.text = "changed"
}
}
class EditorViewController: UIViewController {
#IBOutlet weak var input: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
input.text = "not changed"
}
}

Present NSWindow as sheet from another NSWindow using beginSheet function

I'm building a macOS bundle app that's running at the beginning of the login process and I want to show some information to the user in a window. I have a NSWindowController with a .xib file which it's the main window.
For that, I created an NSViewController with a .xib to define the user interface that I want to present as a sheet, but do not show any UI and never appears
Main NSWindowController looks like:
import Cocoa
class Main: NSWindowController {
var sheetWindow: SheetWindow?
override func windowDidLoad() {
super.windowDidLoad()
self.window!.canBecomeVisibleWithoutLogin = true
self.window!.isMovable = false
sheetWindow = SheetWindow.init(windowNibName: "SheetWindow")
}
#IBAction func btnClick(_ sender: Any) {
self.window!.beginSheet(sheetWindow.window!, completionHandler: { (resp) in
....
})
}
}
==================================================================================
import Cocoa
class SheetWindow: NSWindowController {
override func windowDidLoad() {
super.windowDidLoad()
self.window!.isMovable = false
}
}
The main window looks like:
And after click the button looks like:
I notice after "beginSheet" function is triggered the close button from the main window disappeared
NOTE: "Visible At Launch" property is unchecked in the SheetWindow .xib
Any idea what it could be happening !!!

Present modal from NSWindowController without using Storyboards

I building a macOS framework that's running at the beginning of the login process and I want to show some information to the user in a window. I have an NSWindowController with a .XIB file and it's the main window for my framework.
I would like to add more presentAsSheet to another view. For that, I created an NSViewController with a .XIB to define the user interface that an I want to present in a modal but here I'm completely blocked because getting the contentViewController of the window its always nil
I'm initializing my main window like this:
func run() {
NSApp.activate(ignoringOtherApps: true)
mainWC = MainWC(windowNibName: "MainWC")
guard mainWC.window != nil else {
return
}
NSApp.runModal(for: mainWC.window!)
}
Where Im trying to present the other NSViewController"
self.window?.contentViewController?.presentAsSheet(customViewController)
In my MainWindowController I'm doing this:
#IBAction func btnAction(_ sender: Any) {
let customModal = CustomModal(windowNibName: "CustomModal")
self.window?.beginSheet(customModal.window!, completionHandler: { code in
print(message: "Clicked")
})
}
The modal never appears
You can't present the view until the window and the content view have been loaded
Either inside the window controller in windowDidLoad()
override func windowDidLoad() {
super.windowDidLoad()
// present the view
}
or even in viewDidLoad of the view controller
override func viewDidLoad() {
super.viewDidLoad()
// present the view
}

What is the correct way to make a NSWindowController Singleton in Swift?

I have a sample project as:
https://github.com/ericgorr/nspanel_show.git
My project is a storyboard, document based application. I would like to use a custom segue to toggle the visible state of the inspector window. What I have should work, but I cannot quite determine how to make the inspector window a singleton.
I believe I should start with:
class InspectorWindowController: NSWindowController
{
static let sharedInstance = InspectorWindowController()
// override func init()
// {
//
// }
override func windowDidLoad()
{
super.windowDidLoad()
NSLog( ":::: %#", InspectorWindowController.sharedInstance );
}
}
But exactly what the initialization should look like in my situation is escaping me, especially since the window is inside of a storyboard.
You can select the window controller from the window controller scene and in the attributes inspector select Single from the pop up under Presentation. This will ensure the show segue only uses a single instance of the window controller. See this answer for more information.
Here's how I would modify your code:
In Main.storyboard give your InspectorWindowController an identifier, such as "Inspector Window Controller"
In InspectorWindowController, implement your singleton as follows:
static let shared: InspectorWindowController = {
let storyboard = NSStoryboard(name:"Main", bundle: nil)
let controller = storyboard.instantiateController(withIdentifier: "Inspector Window Controller")
return controller as! InspectorWindowController
}()
In Main.storyboard delete the segue from WindowController to InspectorWindowController
In WindowController replace the showMyPanel() and hideMyPanel() IBActions with:
#IBAction func toggleInspectorPanel( _ sender: AnyObject ) {
let inspectorWindow = InspectorWindowController.shared.window!
if inspectorWindow.isVisible {
inspectorWindow.orderOut(self)
} else {
inspectorWindow.makeKeyAndOrderFront(self)
}
}
Also in WindowController, remove the NSLog() call from windowDidLoad(). It causes a recursive call to the InspectorWindowController.shared initialization code.
In Main.storyboard link the Inspector toolbar button to toggleInspectorPanel()
The InspectorWindowController.shared singleton will be initialized, and the inspector panel loaded (but not shown), the first time it is referenced.

Segue w/ Tab View Controller Keeps Passing Value

I am working on an iOS application that is built around a Tab View Controller. I have created a "Contacts" tab, where a user can find and select a contact from a list. When the user selects the contact, it takes the contact's name and passes it to a different tab. That function is being done like so:
func passName(name: String) {
let navTab = self.tabBarController!.viewControllers![2] as! UINavigationController
let homeTab = navTab.viewControllers[0] as! MainController
homeTab.passedName = name
tabBarController?.selectedIndex = 2
}
Everything works as it should so far (name is loaded into text field). My issue is that the value seems to keep coming back every time I change tabs and then go back to my Home tab. For example, if I select "John" from my contacts, it will take me to the Home Tab and put John's name in a textfield. Let's say I delete the last two letters of the name, so now it is "Jo". If I load a different tab and come back, the name field has been reset to "John". It's as if the value gets re-passed every time I open the Home Tab. Also, every time I load the Home Tab after passing a name, my console prints: "Name Passed: John", so it shows that this is being processed every single time the tab appears. Here is my code for processing the name:
var passedName: String!
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
//Checks if name was passed to controller
if let validName = passedName {
print("Name passed: \(validName)")
nameTextField.text = validName
}
}
Am I passing the data incorrectly? I was thinking it might be because I have the above code being called in the viewWillAppear method, but that doesn't make sense, as essentially the data is only being passed one time from the Contacts tab. Thanks!
The problem is that you're not actually passing the value back to the original view. Apple's recommendation for passing information between classes is to use the delegate pattern. This allows the modal view to call the delegate class's function, which changes the name local to the original view because that function is declared in the original view's viewController. You can read more about the pattern in this tutorial, but I've also included a brief example relevant to your use case below.
mainViewController:
class namesTableViewController: UITableViewController, editNameDetailsViewControllerDelegate {
var name : String
#IBAction func editButtonPressed(_ sender: UIBarButtonItem) {
performSegue(withIdentifier: "editPerson", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "editPerson" { //Modal segue
let navController = segue.destination as! UINavigationController
let controller = navController.topViewController as! editNameViewController
controller.delegate = self
if let person = sender as? Person {
print("Sending person to edit")
controller.personToEdit = person
}
} else {
super.prepare(for: segue, sender: sender)
}
}
//Protocol function
func changeName(n: String, controller: UIViewController) {
name = n
dismiss(animated: true, completion: nil)
}
}
editNameViewController:
class editNameViewController: UIViewController {
#IBOutlet weak var personNameTextField: UITextField!
var personToEdit : Person?
weak var delegate : PersonTableViewController?
override func viewDidLoad() {
super.viewDidLoad()
if personToEdit != nil {
personNameTextField.text = personToEdit?.name
}
}
// Button Actions
#IBAction func saveButtonPressed(_ sender: UIBarButtonItem) {
delegate?.personDetailsView(n: personNameTextField.text, controller: self)
}
}
Finally, the protocol class :
protocol editNameDetailsViewControllerDelegate : class {
func personDetailsView(n: String, controller: UIViewController)
}
Hope this helps.
The problem is "passedName" variable doesn't changed its value every time you edit it in your UITextField. Keep in mind that every time you change tabs, the UIViewController will call viewWillAppear and viewDidAppear. So your UITextField will always show passedName value once you select other tab and return.
I suggest that every time you edit the textfield you should update passedName value.
Sorry for my bad english.