I have a IBOutlet of a NSToolBar button in my NSWindowController class, which is my main window class:
class MainWindowController: NSWindowController {
#IBOutlet weak var myButton: NSButton!
// ...
}
I have a class MainViewController that is that content NSViewController of the main window.
How can I access this button in my content NSViewController? Is there a better way to organize the IBOutlets and the controllers to facilitate this access?
To access NSViewController from NSWindowController:
let viewController:MainViewController = self.window!.contentViewController as! MainViewController
To access NSWindowController from NSViewController:
let windowController:MainWindowController = self.view.window?.windowController as! MainWindowController
How about like this using delegate? This example will change your button's title.
#objc protocol SomeDelegate {
func changeTitle(title: String)
}
class ViewController: NSViewController {
weak var delegate: SomeDelegate?
#IBAction func myAction(sender: AnyObject) {
delegate?.changeTitle("NewTitle")
}
}
class MainWindowController: NSWindowController, SomeDelegate {
#IBOutlet weak var myButton: NSButton!
override func windowDidLoad() {
super.windowDidLoad()
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
let myVc = window!.contentViewController as! ViewController
myVc.delegate = self
}
func changeTitle(title: String) {
myButton.title = title
}
}
Related
Generally, We can able to display next view controller from first view controller by having different kind of NSStoryboardSeque like Present, Show, Sheet etc., But, How we can achieve the same programmatically?.
Comparing with UIViewController, presenting a view controller modally by presentViewController:animated:. Is there any same kind of approach for NSViewController?
Thanks in advance.
The two different presentation types I use are:
func presentViewControllerAsModalWindow(_ viewController: NSViewController)
func presentViewControllerAsSheet(_ viewController: NSViewController)
After doing some more research another way to do using:
func presentViewController(_ viewController: NSViewController, animator: NSViewControllerPresentationAnimator)
And eating a custom presentation animator. Here you have the freedom to do what you like :)
In case someone is looking for the solution in 2022,
extension NSViewController {
func presentInNewWindow(viewController: NSViewController) {
let window = NSWindow(contentViewController: viewController)
var rect = window.contentRect(forFrameRect: window.frame)
// Set your frame width here
rect.size = .init(width: 1000, height: 600)
let frame = window.frameRect(forContentRect: rect)
window.setFrame(frame, display: true, animate: true)
window.makeKeyAndOrderFront(self)
let windowVC = NSWindowController(window: window)
windowVC.showWindow(self)
}
}
1.Create a NSViewController instance with StoryBoard Identifier
let theTESTVCor = self.storyboard?.instantiateController(withIdentifier: "TESTVCor") as! NSViewController
2.Present In Via the current NSViewController
theNSViewController.presentViewControllerAsModalWindow(theTESTVCor)
⚠️ DO NOT FORGET to set the Identifier of the NSViewController in Storyboard
If you have a view controller (presenting) than it's as simple as following function are provided:
open func presentAsSheet(_ viewController: NSViewController)
open func presentAsSheet(_ viewController: NSViewController)
open func present(_ viewController: NSViewController, asPopoverRelativeTo positioningRect: NSRect, of positioningView: NSView, preferredEdge: NSRectEdge, behavior: NSPopover.Behavior)
If you need to present a view controller in a new window (NOT MODAL) you need to create own NSWindow, NSWindowController
let gridView = NSGridView(views: [
[NSTextField(labelWithString: "label1"),NSTextField(labelWithString: "label2")],
[NSTextField(labelWithString: "label3"),NSTextField(labelWithString: "label4")]
])
let viewController = NSViewController()
viewController.view = gridView
let window = NSWindow(contentViewController: viewController)
window.center()
let windowController = NSWindowController(window: window)
windowController.showWindow(nil)
EXPLANATION:
Storyboards are using seques to perform some magic. The show seque is simply calling action "perform:" on object NSStoryboardShowSegueTemplate ([NSApp sendAction:to:from]). This seque will create NSWindowController and NSWindow (private method windowWithContentViewController:) for you and on top it will layoutSubviews/resize and center the window. Magic bonus is self retaining the window so you don't care about memory management.
Example of programatic calling (using Storyboards to instantiate windowController with viewController)
import Cocoa
import Contacts
class ShorteningHistoryWindowController : NSWindowController, Storyboarded {
static var defaultStoryboardName = "ShorteningHistory"
}
struct ShorteningHistory {
static let shared = ShorteningHistory()
private var windowController : NSWindowController
private init() {
windowController = ShorteningHistoryWindowController.instantiate()
}
public func showHistory() {
windowController.showWindow(self)
}
}
extension Storyboarded where Self: NSWindowController {
static var defaultStoryboardName: NSStoryboard.Name { return String(describing: self) }
static var defaultIdentifer: NSStoryboard.SceneIdentifier {
let fullName = NSStringFromClass(self)
let className = fullName.components(separatedBy: ".")[1]
return className
}
static func instantiate() -> Self {
let storyboard = NSStoryboard(name: defaultStoryboardName, bundle: Bundle.main)
guard let vc = storyboard.instantiateController(withIdentifier: defaultIdentifer) as? Self else {
fatalError("Could not instantiate initial storyboard with name: \(defaultIdentifer)")
}
return vc
}
}
PS: Don't forget to set Storyboard Identifiers in Storyboard
I am new to Mac OSX and with Apple promoting the fact that the bodies of code are becoming similar decided to tell the folk I am writing code for we should be able to do a Mac OSX version. iPhone and iPad versions are all good and about to release second version so no issues there.
So I am subclassing NSWindowController to get access to the Toolbar and worked out how to remove and add items on the toolbar, but for the life of me I can not get one NSViewController (firstViewController) to dismiss and bring up the second NSViewController (secondViewController) in the same NSWindowController.
So the 2 issues are that
1. I want to be able to performSegueWithIdentifier from the first NSViewController in code and
2. bring up the second NSViewController by replacing the first NSViewController in the same NSWindowController.
If I add a button to the firstViewController and put a segue to the secondViewController then when I select the button the secondViewController comes up just fine but in a seperate window not the same NSWindowController that I want it to and the firstViewController does not get replaced but stays in the NSWindowController.
So I know the segue idea will work but its not working in code and when I do insert the segue from a button it works but into a seperate NSViewController that is not part of the NSWindowController.
I am trying to find some programming guide from Apple on the issue but no luck so far.
Here is an overview from my Storyboard:
Here is my NSWindowController subclassed and the func loginToMe2Team is trigger from the NSToolBar and its working just find as the print statements show up on the console.
import Cocoa
class me2teamWindowsController: NSWindowController {
#IBOutlet var mySignUp : NSToolbarItem!
#IBOutlet var myToolbar : NSToolbar!
let controller = ViewController()
override func windowDidLoad() {
super.windowDidLoad()
print("window loaded")
}
override func windowWillLoad() {
print("window will load")
}
#IBAction func logInToMe2Team(sender: AnyObject){
controller.LogIn() //THIS IS THE FUNC I AM TESTING WITH
}
#IBAction func signUpToMe2Team(sender: AnyObject){
controller.signUp()
}
Here is my NSViewController subclassed with the func LogIn. Its getting selected just fine but the performSegueWithIdentifier is not. And I did cut and past the Identifier to make absolutely sure it was the same.
import Cocoa
import WebKit
class ViewController: NSViewController {
#IBOutlet weak var theWebPage: WebView!
#IBOutlet weak var progressIndicator: NSProgressIndicator!
override func viewDidLoad() {
super.viewDidLoad()
let urlString = "https://thewebpage.com.au"
self.theWebPage.mainFrame.loadRequest(NSURLRequest(URL: NSURL(string: urlString)!))
}
override func viewDidAppear() {
}
func LogIn() {
print("I logged in")
self.performSegueWithIdentifier("goToTeamPage", sender: self)
//THIS IS THE BIT THATS NOT WORKING
}
func signUp() {
print("I have to sign up now")
}
override var representedObject: AnyObject? {
didSet {
}
}
func webView(sender: WebView!, didStartProvisionalLoadForFrame frame: WebFrame!)
{
self.progressIndicator.startAnimation(self)
}
func webView(sender: WebView!, didFinishLoadForFrame frame: WebFrame!)
{
self.progressIndicator.stopAnimation(self)
}
}
You need to use a custom segue class (or possibly NSTabViewController if it’s enough for your needs). Set the segue’s type to Custom, with your class name specified:
…and implement it. With no animation, it’s simple:
class ReplaceSegue: NSStoryboardSegue {
override func perform() {
if let src = self.sourceController as? NSViewController,
let dest = self.destinationController as? NSViewController,
let window = src.view.window {
// this updates the content and adjusts window size
window.contentViewController = dest
}
}
}
In my case, I was using a sheet and wanted to transition to a different sheet with a different size, so I needed to do more:
class ReplaceSheetSegue: NSStoryboardSegue {
override func perform() {
if let src = self.sourceController as? NSViewController,
let dest = self.destinationController as? NSViewController,
let window = src.view.window {
// calculate new frame:
var rect = window.frameRectForContentRect(dest.view.frame)
rect.origin.x += (src.view.frame.width - dest.view.frame.width) / 2
rect.origin.y += src.view.frame.height - dest.view.frame.height
// don’t shrink visible content, prevent minsize from intervening:
window.contentViewController = nil
// animate resizing (TODO: crossover blending):
window.setFrame(window.convertRectToScreen(rect), display: true, animate: true)
// set new controller
window.contentViewController = dest
}
}
}
This is the code for UITabBarController in which I am trying to open a splitviewcontroller.
import UIKit
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
///here after this line I am getting error
var mainCont : UITabBarController = ((UIApplication.sharedApplication().delegate) as! AppDelegate).window?.rootViewController as! UITabBarController
var navCont2 : UINavigationController? = mainCont.viewControllers?[1] as? UINavigationController
var controller = UIStoryboard(name: "Storyboard2", bundle: nil).instantiateInitialViewController() as! UISplitViewController
controller.preferredDisplayMode = UISplitViewControllerDisplayMode.AllVisible
navCont2?.presentViewController(controller, animated: true, completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
Your first line:
var mainCont : UITabBarController = ((UIApplication.sharedApplication().delegate)
as! AppDelegate).window?.rootViewController as! UITabBarController
Is getting the window's rootViewController as UITabBarController. And the error message is really kind of clear:
Could not cast value of type 'UINavigationController' (0x10836e698) to 'UITabBarController' (0x10836e6e8).
On app start the window's rootViewController is set to what ever you have defined as your initial view controller on the Storyboard (The big grey arrow). My guess is that your initial view controller is set to a UINavigationController, but in your code you are trying to cast (force) it to be a UITabBarController.
probably I don't understand how views works in swift. I'm trying to replace view in my LoginViewController but nothing is happening.
#IBAction func loginBtn(sender: AnyObject) {
let authViewController = AuthViewController(nibName: "AuthView", bundle: nil)!
let appDelegate = NSApplication.sharedApplication().delegate as AppDelegate
appDelegate.window.contentView.replaceSubview(self.view, with: authViewController.authView)
}
and my AppDelegate.swift
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
#IBOutlet weak var customView: NSView!
#IBOutlet weak var statusMenu: NSMenu!
func applicationDidFinishLaunching(aNotification: NSNotification) {
let loginViewController = LoginViewController(nibName: "LoginView", bundle: nil)!
window.contentView.addSubview(loginViewController.view)
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
}
I'm not sure but will it help if you tell frame after your replace subview?
let authViewController = AuthViewController(nibName: "AuthView", bundle: nil)!
let appDelegate = NSApplication.sharedApplication().delegate as AppDelegate
appDelegate.window.contentView.replaceSubview(self.view, with: authViewController.authView)
authViewController.view.frame = (appDelegate.window.contentView as! NSView).frame
loginViewController will be deallocated right away as nobody is holding it. Same is true for authViewController
=> VIEWS don't hold their VIEW CONTROLLERS so the views will go away right away
make a member variable to hold on to them
Im trying to write an OSX app. A functionality of this app is that it displays the machine IP address.
The address is fetched when the program is opened (AppDelegate.swift):
#NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate {
var ipadd:String = ""
var path:String = ""
var status:String = ""
func applicationDidFinishLaunching(aNotification: NSNotification) {
ipadd = getIFAddress() //<-- ip stored in here as String
println(ipadd) //successfully prints out the ip
ViewController.setIpDisp(ipadd) //error on this line
}
...
}
And in ViewController.swift:
class ViewController: NSViewController {
#IBOutlet weak var ip: NSTextField!
...
func setIpDisp(ipin: String){
ip.stringValue = ipin
}
To be exact, the error is "Cannot invoke 'setIpDisp' with an argument list of type '(String)'
Thanks
The AppDelegate is trying to call a ViewController method that is updating an #IBOutlet in the view controller's view. It needs a valid ViewController instance to do that.
But this is backwards: The app delegate should not be trying to call view controller methods. The view controller can call methods/properties of the app delegate, but the app delegate really shouldn't be calling view controller methods.
If you need to update the IP number field in the view controller, then the view controller should be initiating this (e.g. in viewDidLoad):
class ViewController: NSViewController {
#IBOutlet weak var ip: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
updateIpDisp()
}
func updateIpDisp() {
let appDelegate = NSApplication.sharedApplication().delegate as! AppDelegate
ip.stringValue = appDelegate.getIFAddress()
}
}
Or, if you wanted, the AppDelegate set some ipadd string property in its init method (not applicationDidFinishLaunching), and then the updateIpDisp() method could retrieve the property's value from the app delegate, too. (Given that IP numbers are dynamic and can change, that doesn't seem right to me, but do it however you want.) Anyway, that might look like:
class AppDelegate: NSObject, NSApplicationDelegate {
var ipadd: String!
override init() {
super.init()
ipadd = getIFAddress()
}
}
and
class ViewController: NSViewController {
#IBOutlet weak var ip: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
updateIpDisp()
}
func updateIpDisp() {
let appDelegate = NSApplication.sharedApplication().delegate as! AppDelegate
ip.stringValue = appDelegate.ipadd
}
}
But the view controller should be requesting the IP number from the app delegate and updating its own view. But the app delegate has no business calling methods in the view controller(s).
Your function isn't static, so make sure to initialise an instance of it, like so
#NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate {
let viewController = ViewController()
var ipadd:String = ""
var path:String = ""
var status:String = ""
func applicationDidFinishLaunching(aNotification: NSNotification) {
ipadd = getIFAddress() //<-- ip stored in here as String
println(ipadd) //successfully prints out the ip
viewController.setIpDisp(ipadd) //error on this line
}
...
}