How to enable undo menu item when a sheet is presented - cocoa

I am creating a document based core data OSX app using storyboards. Undo and redo works fine until I present a View Controller in a sheet with a segue. Once the sheet is presented, the undo / redo buttons are grayed out.
While searching for a possible solution, I came across this article in which they say that I have to supply an undo manager to my window using the
"windowWillReturnUndoManager:" delegate method. So I implemented this method in the sourceController of my segue, and set that controller as the delegate for the window of the destinationController in the prepareForSegue method like this:
override func prepareForSegue(segue: NSStoryboardSegue, sender: AnyObject?) {
super.prepareForSegue(segue, sender: sender)
(segue.destinationController as NSViewController).view.window?.delegate = self
}
func windowWillReturnUndoManager(window: NSWindow) -> NSUndoManager? {
println(undoManager)
return undoManager
}
But the undo and redo buttons are still grayed out when I open the sheet. Note that when change the segue style to popover, the undo/redo are working perfectly. How can I resolve this?

I had the same problem and am posting here in case it might help someone. My solution was to obtain the undo manager for the window to which the sheet view controller is attached:
let undoManager = self.view.window?.firstResponder?.undoManager
self in this context is the view controller for the sheet and not the parent view controller for the sheet. Therefore, the assignment would take place within the view controller for the sheet.

Related

Closing window in swift

I have a collection view and each item opens a new window with a slideshow.
The segue kind is "show" and it's opened with:
performSegue(withIdentifier: "showGalleryPlayer", sender: self)
My idea is when double click another item the window with the previous slideshow close and the new one opens. I don't know how to do it or if it is the right approach. I want only one window with the slideshow at a time.
Thanks.
When performing a segue, the following method will be invoked:
func prepare(for segue: NSStoryboardSegue, sender: Any?)
Just override this method in whichever class you perform the segue and use the provided NSStoryboardSegue object to retrieve a reference to the newly opened window (after matching the segue's identifier of course). Store the reference to the window and use it to close the window before opening the next one.

Dynamically changing a NSWindow's contentViewController

I have an NSWindowController and an NSViewController, both configured in a storyboard.
When I set the contentViewController in Interface Builder, everything works as expected.
When I set the contentViewController programmatically in windowDidLoad everything works as expected.
Now I'm trying to update the content view controller in response to a toolbar button click (below).
#IBAction func toolbarButtonClicked(_ sender: Any) {
contentViewController = storyboard?.instantiateController(withIdentifier: "VC") as! NSViewController?
}
This assignment causes viewWillAppear to be called on the view controller, but at that point the content view doesn't have a window assigned.
Why is this? Is it possible to dynamically change a window's contentViewController like this?
It looks like:
When the content view is configured in storyboard or windowDidLoad, the view already has a window by the time viewWillAppear gets called, i.e. when the window itself appears.
When the content view is set after the window has appeared, viewWillAppear gets called to indicate that the content view will be added to the window; the view has no window until viewWillLayout is called.
I was using the window to access the current document, so my solution was to access the document before updating the content and use that to configure the content view controller.

How to dismiss or unwind a view in Swift (Xcode 6)?

I have been scouring the web for the best and most proper way to dismiss/unwind a view using Swift and cannot find a definitive answer. For ease of concept, I have two views and have linked one to the other with a button and a segue using "show." How do I return to the original view (no data needs to be passed)?
you can try adding a button action to your ViewController2 and add in the code below. Your ViewController2 will dismiss when the button is being pressed.
dismissViewControllerAnimated(true, completion: nil)

Failed to connect (storyboard) outlet from (NSApplication) to (NSNibExternalObjectPlaceholder) error in Cocoa and storyboard

I've tried to build a sample Cocoa app on which I want to connect UI components put on storyboard to ViewController.swift as either an IBOutlet or IBAction. However, when I tried to control-drag the UI components on storyboard (such as NSButton) to ViewController.swift and create a #IBAction method, and then run the app, the resultant app logs the following message in console and of course the app doesn't respond to me tapping the button.
Failed to connect (storyboard) outlet from (NSApplication) to (NSNibExternalObjectPlaceholder): missing setter or instance variable
How can I use the IBAction method properly?
For your information here's my ViewController.swift:
import Cocoa
class ViewController: NSViewController {
#IBOutlet var txtTitle : NSTextField
#IBOutlet var boxColor : NSBox
override func viewDidLoad() {
super.viewDidLoad()
}
func colorChanged(cp: NSColorPanel) {
let c:NSColor = cp.color;
self.boxColor.fillColor = c
}
#IBAction func btnSetColor(sender : AnyObject) {
let cp:NSColorPanel = NSColorPanel.sharedColorPanel()
cp.setTarget(self)
cp.setAction("colorChanged:")
cp.orderFront(nil)
}
#IBAction func btnSetWindowTitle(sender : AnyObject) {
if self.txtTitle.stringValue != "" {
println(self.title)
println(self.txtTitle.stringValue)
self.title = self.txtTitle.stringValue
}
}
}
I use Xcode 6 beta on OS X 10.10 Yosemite. And started the template with storyboard being on.
While the answer above correctly states that this isn't the reason for compilation issues, I thought that I would clarify for those who are just looking to eliminate the warning messages altogether. This was what I was looking for when I found this page.
When you are building your actions and some of the actions change, or get deleted in the storyboard, the outlets remain. Select the controller/window where the older unused actions used to be and you will still see them in the outlets segment of the storyboard within the attributes tab. Remove those old actions/outlets there and then the warning disappear.
Look for duplicates between the ViewController and the File's Owner. One or both might be holding on to these objects when they shouldn't be. Removing those will remove these soft warnings.
Failed to connect (storyboard) outlet from (NSApplication) to (NSNibExternalObjectPlaceholder): missing setter or instance variable
The IBAction methods working like it should, see Apple Dev Forums:
"This is a known issue ... The messages are harmless and do not
indicate a problem with your code."
Apple Dev Forums: OS X Storyboard failure
Thats not why your code is not working, you need to fix the following:
A) Here is my working code to set the title - using self.view.window.title instead self.title:
#IBAction func btnSetWindowTitle(sender : AnyObject) {
if self.txtTitle.stringValue != "" {
println(self.view.window.title)
println(self.txtTitle.stringValue)
self.view.window.title = self.txtTitle.stringValue
}
}
B) In Interface Builder you need to set NSBox "Box Type" to "Custom":
And that's it:
I think I figured out the right solution.
1) Drag an Object into you xib interface.
2) Click the Object in the left list you just dragged in.
3) Bind the Object to your custom class.(Here my class is a login window controller as example)
4) Ctrl drag button to the source code. In the popup window, choose your class name(here in example is Login Window Controller) rather than File's Owner.
Hope this could help you.
I've found another easier solution these days while coding.
Check this out.
1) Select File's Owner in Document Outline in the .xib file.
2) Specify the class you want the .xib file to connect with.
3) Now when you connect outlet to the source file, just use default File's Owner. Much easier.
4) I guess it's not enough so far. I've met an exception when running called 'loaded the 'xxx' nib but the view outlet was not set'. We should do something more.
Select the view in Document Outline. Drag from the circle of New Referencing Outlet to the File's Owner in Document Outline.
Alright, that's the new easier solution. No additional objects should add into the xib. If it doesn't work, leave comments below.

Does anyone know what the new Exit icon is used for when editing storyboards using Xcode 4.5?

Right-clicking the Exit icon yields an empty window. Can't Ctrl-drag a connection to any IB elements or corresponding source files. Docs give no love. Doesn't appear in nib files, only storyboards. My assumption is that it's a corollary to segues, but I don't see any new methods to back it up. Anyone?
I had a hard time following the accepted answer so here is more detail.
Given the photo below on view controller C you can "exit" back to any view controller in the segue path.
ViewController A you can write:
- (IBAction)done:(UIStoryboardSegue *)segue {
// Optional place to read data from closing controller
}
ViewController B you can write:
- (IBAction)back:(UIStoryboardSegue *)segue {
// Optional place to read data from closing controller
}
ViewController C you control drag from "back" button to the green exit option and select back:
ViewController C you control drag from "done" button to the green exit option and select done:
Note: Even though the methods are on other view controllers they show up for the ViewController C's exit. Control dragging and selecting a method defines which ViewController to unwind to.
There's a lot of information in the WWDC video "Session 407 - Adopting Storyboards in your App."
Say you have two view controllers linked by a segue. Implement the following exit action on the first view controller:
- (IBAction)done:(UIStoryboardSegue *)segue {
NSLog(#"Popping back to this view controller!");
// reset UI elements etc here
}
Then, on Storyboard scene for the second view controller, Ctrl-drag from a UI element, such as a button, to the exit icon at the bottom of this view controller. The done: action you added to the code of the first controller will appear as an option. Now, activating the button you Ctrl-dragged to the exit icon will pop back to the first view controller and maintain its original state (ie UI elements such as text input supposedly still intact).
As addition to Eric answer here is how it works with swift:
The function you add to the destination controller looks like:
#IBAction func backFromOtherController(segue: UIStoryboardSegue) {
NSLog("I'm back from other controller!")
}

Resources