NSTableCellView inside a custom view refuses first responder - cocoa

Instead of a TableView I want to use a NSTableCellView inside a custom view. So I created a xib file with a standard NSTableCellView and loaded that into a view.
The table cell view is displayed as expected. But I can't make its textfield the first responder. It's not responding to mouse events and it's not even reacting when I explicitly make it the first responder. makeFirstResponder returns true, but I see no blinking cursor and it doesn't respond to any key events.
Adding a regular textfield to the view does work however.
class ViewController: NSViewController
{
#IBOutlet var myView: NSView!
#IBOutlet var cellView: NSTableCellView! //ViewController is the file owner of the xib file
override func viewDidLoad()
{
super.viewDidLoad()
NSBundle(forClass: ViewController.self).loadNibNamed("Cell", owner: self, topLevelObjects: nil)
myview.addSubview(cellView)
cellView.layer?.borderColor = Color.blackColor().CGColor
cellView.layer?.borderWidth = 1
}
override func mouseDown(event: NSEvent)
{
let result = self.view.window?.makeFirstResponder(cellView.textField!)
print(result) //prints true
}
Why am I doing this? Well, I am trying to understand how a view-based table view would be implemented. It seems that at its core a tableview is just a view with lots of (probably cached and reused?) subviews. Also, I always thought that the number of views was limited for efficiency purposes on OS X?

Related

How can I show the marked text from one textfield as marked text in other textfield in Xcode, swift 2

I have two textfield having two texts in my OS X program. I would like to have the following effect. When I marked part of text in one textfield I would like to have the identical marked part of the text in other textfield. How can I implement this in xcode and using swift 2?
You can create two TextViews (note: class NSTextView not NSTextField) and connect them to a view controller using outlets. Again, connect the innermost TextView, not the clip view or bordered scroll view. In the example below "searchView" is the one that contains the text you select, and "textView" the one which contains the text you want to highlight.
Then, you can do something like this (note that I tested this in Swift 1.2)
class ViewController: NSViewController {
#IBOutlet var textView: NSTextView!
#IBOutlet var searchView: NSTextView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
NSNotificationCenter.defaultCenter().addObserver(self, selector: "selectedSomeText:", name: NSTextViewDidChangeSelectionNotification, object: searchView)
}
#objc func selectedSomeText(notification: NSNotification) {
let selectedRange = searchView.selectedRange()
if selectedRange.length > 0 {
let nsString = searchView.string! as NSString
let selectedString = nsString.substringWithRange(selectedRange)
let regex = NSRegularExpression(pattern: selectedString, options: nil, error: nil)
let foundRanges = regex?.matchesInString(textView.string!, options: nil, range: NSRange(location: 0, length: count(textView.string!)))
let firstFound = foundRanges![0] as! NSTextCheckingResult
textView.showFindIndicatorForRange(firstFound.range)
}
}
}
This will highlight the first found match. You can then implement buttons or something to iterate though matches (for example, by storing "foundRanges" somewhere and iterate its index using buttons, etc). You might also want to implement some error checking and/or optional chaining because all those nils and forced downcasts in my example might cause crashes.

Pass continuous colorwell data to second view controller

I have the following situation:
two ViewControllers each containing a box that is to be colored to a color picked from a color well in ViewController
The colorwell is set as continuous in order to see the changes reflected immediately
I am looking for a way to continuously pass the color well value on to the SecondViewController and on to a callback method that will color a box in the SecondViewController.
I found that the prepareForSegue method is commonly used to pass data between view controllers, this however only occurs once during the transition and not continuously.
Can someone point me out in the right direction? Googled for hours but I got really stuck with this.
Thanks.
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var box: NSBox!
#IBOutlet weak var well: NSColorWell!
#IBAction func well(sender: AnyObject) {
box.fillColor = well.color
}
override func prepareForSegue(segue: NSStoryboardSegue, sender: AnyObject?) {
let second = segue.destinationController as! SecondViewController
second.representedObject = well.color
}
}
import Cocoa
class SecondViewController: NSViewController {
#IBOutlet weak var box: NSBox!
override func viewWillAppear() {
// Note that box.fillColor requires box type to be custom
box.fillColor = self.representedObject as! NSColor
}
}
The prepareForSegue method is a chance to create links between two view controllers. It's pretty common for the source view controller to set itself up as the delegate of the destination view controller. It's also possible for the source view controller to save a reference to the destination view controller for future reference.
If you define a protocol with a method like
func colorValueHasChanged(newColor: NSColor)
Then you can use it in the IBAction for your color well to pass information about changes in the color well from one view controller to the other.

how to presentViewControllerAsSheet on OSX Mavericks?

It's a long story, but to cut it short; my first OSX app was written (on Yosemite) in Swift using a storyboard until I found out my (finished) app will not run on Mavericks. I need to run on Mavericks, so I have replaced the storyboard with NIBs.
My problem is with the segues; I was using 'sheet type' segues to show other view controllers in a sheet over the main view controller. A call to the presentViewControllerAsSheet method of NSViewController is a good replacement as it looks the same, but this API was introduced in Yosemite - so I need to work out how to do this for Mavericks.
In the action for a button on the main view, I've tried using beginSheet like this:
secondViewController = SecondViewController(nibName: "SecondViewController", bundle: nil)
self.view.window?.beginSheet(secondViewController!view.window!, completionHandler: nil)
But the second view controller's window is null at runtime. I've tried adding the new view controller as a subview to the application window but this is an unrecognised selector:
NSApplication.sharedApplication().windows[0].addSubView(secondViewController!.view)
I've search high and low for a description of how to show a sheet and all I can find is: Can a view controller own a sheet? but I'm sorry to admit I don't understand the answer. Can anybody help me with some working code? I'm beginning to worry that I'm trying to do something unusual but it looks OK on Yosemite, so how did people do this before Yosemite was released?
EDIT
I still haven't got to the solution, so I have put together a small app which shows the problems I'm having.
In AppDelegate.swift:
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
var mainViewController: FirstView!
func applicationDidFinishLaunching(aNotification: NSNotification) {
mainViewController = FirstView(nibName:"FirstView", bundle: nil)
window.contentView = mainViewController.view
mainViewController.view.frame = (window.contentView as! NSView).bounds
}
}
In FirstView.swift (associated NIB has a 'open sheet' button)
class FirstView: NSViewController {
var secondView: SecondView?
var secondWindow: SecondWinCon?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func pressButton(sender: AnyObject) {
secondView = SecondView(nibName: "SecondView", bundle: nil)!
// method 1 - this is the behaviour I want (but it only works on OSX 10.10)
// presentViewControllerAsSheet(secondView!)
// method 2 - this just creates a floating window
// self.view.addSubview(secondView!.view)
// self.view.window?.beginSheet(secondView!.view.window!, completionHandler: nil)
// method 3 - this also creates a floating window
secondWindow = SecondWinCon(windowNibName: "SecondWinCon")
self.view.window?.beginSheet(secondWindow!.window!, completionHandler: nil)
}
}
In SecondView.swift (associated NIB has a 'close' button)
class SecondView: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func dismissPressed(sender: AnyObject) {
if (presentingViewController != nil) {
presentingViewController?.dismissViewController(self)
} else {
self.view.window?.sheetParent?.endSheet(self.view.window!)
}
}
}
In SecondWinCon.swift (Associated NIB is empty)
class SecondWinCon: NSWindowController {
var secondView: SecondView?
override func windowDidLoad() {
super.windowDidLoad()
secondView = SecondView(nibName: "SecondView", bundle: nil)!
self.window?.contentView.addSubview(secondView!.view)
}
}
If method 1 is uncommented, you will see the behaviour I'm trying to emulate (remember it only works on OS X 10.10). Method 2 or 3 displays the second view, but not as a sheet.
I have the same problem, and found maybe is't an issue related to view life cycle.
When I call presentViewControllerAsSheet in viewDidLoad, sheet will not shown, and you will get this in console:
Failed to set (contentViewController) user defined inspected property on (NSWindow): presentViewController:animator:: View '''s view is not in a window/view hierarchy.
If you trigger this in viewWillAppear or viewDidAppear, it's totally no problem.
UPDATE
Okay, let's make it clear.
For this initial storyboard, NSWindowController is connected with a view controller, think this as a root view controller (RootVC).
Create another view controller desired as a sheet in storyboard (SheetVC).
in viewWillAppear or viewDidAppear of RootVC, [self presentViewControllerAsSheet: SheetVC]
The sheet will show, no additional code required.
If you get here looking for a solution, I was nearly there with method 3. The important step I had missed was to turn off "Visible At Launch" in the NSWindowController's NIB (it's an attribute of the NSWindow). In my sample code, this was in SecondWinCon.nib.

Setting minimum width of NSSplitViews

I'm having a heck of a time setting up a simple split view. The first split view is collapsed. I need to set a minimum width for it. Everything I see online (scarce for NSSplitViewController/NSSplitView) is for Objective-C, puts everything in the app delegate, and uses XIBs.
Here's the scenario:
Window Controller with a segue to a SplitView Controller, which has two split views (2 view controllers).
Which object needs to have the NSSplitViewDelegate?
EDIT: Adding code snippet:
For example, I have this:
import Cocoa
class ViewController: NSSplitViewController, NSSplitViewDelegate {
#IBOutlet weak var pdlSplitView: NSSplitView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func splitView(splitView: NSSplitView, constrainMinCoordinate proposedMinimumPosition: CGFloat, ofSubviewAt dividerIndex: Int) -> CGFloat {
return proposedMinimumPosition + 200
}
}
Is there more that I'm missing?
Thanks
UPDATE
Based on comments below, I've made a change, but now I get a sigAbort on the class definition for the AppDelegate. Full code
ViewController:
import Cocoa
class ViewController: NSSplitViewController, NSSplitViewDelegate {
#IBOutlet weak var pdlSplitView: NSSplitView!
let publicDataListings : PDL = PDL()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.pdlSplitView.delegate = self
}
override func splitView(splitView: NSSplitView, constrainMinCoordinate proposedMinimumPosition: CGFloat, ofSubviewAt dividerIndex: Int) -> CGFloat {
return proposedMinimumPosition + 200
}
}
SidebarViewController:
import Cocoa
class SidebarViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
}
}
DatasetViewController:
import Cocoa
class DatasetViewController: NSViewController, NSSplitViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
}
}
Update
I took away my custom NSSplitViewController class and created two NSSplitView classes, one with the constraint method. Now, I see both subviews, but they're far smaller than they should be:
Is there anyone at all that has done this with Swift and Storyboards?
No coding is required to set a minimum width in a storyboard with auto layout for a NSSplitViewController/NSSplitView.
Select the CustomView that you require a minimum width for (e.g. 200), and add a width constraint set to the required value which will add a "Equal" constraint (e.g. Custom View.Width equals 200).
Next locate that new constraint and change the constraint relation to "Greater Than or Equal" (e.g. so you now have width ≥ 200).
You now have a minimum width in an NSSplitView. You can then use the Priority field to resolve any conflicts with any other auto layout constraints.
These values are not exposed in the storyboard, which is a great shame, but NSSplitViewItem has minimumThickness and maximumThickness properties which you can use. (This overrides the holding priority, so if you set minimumThickness for one splitViewItem, the other one(s) will now shrink into nothing if you make the window small enough.)
There is also automaticMaximumThickness (I cannot work out how this interacts with the other values) and preferredThicknessFraction which had no effect when I played with it under 10.13.
Set NSSplitViewController as delegate of NSSplitView (the split view you want to constrain). In your case it should be - in xib hook the delegate outlet of the NSSplitView to file owner (I guess the file owner is NSSplitViewController subclass)
Implement
- (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)dividerIndex { ... }
in NSSplitViewController
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/NSSplitViewDelegate_Protocol/index.html#//apple_ref/occ/intfm/NSSplitViewDelegate/splitView:constrainMinCoordinate:ofSubviewAt:

Add multiple subviews to NSWindow in Swift

I have a window with an outlet and a custom view (in my .xib file) which contains a button and a text field. When a button is pressed in the window I want to add an instance of the custom view into the window.
Currently I have an outlet to the window and the custom view (configWindow and customView) and this action is called when the button is pressed:
#IBAction func addView(sender: NSButton) {
configWindow.contentView.addSubview(customView)
// Print the views in the window, see what's been added
for i in configWindow.contentView.subviews {
println(i)
}
}
This will only ever add one view to the window.
Is this the right way to go about it, or should I be using a completely different approach?
You can't add the same view twice. It sounds like you are trying to add the same instance of customView to configWindow multiple times, which you can't do. If you think about it, it's fairly obvious why -- how will the superview manage two subviews which are the same? How will it know the difference between the two of them?
You should be adding different instances of the CustomView class instead:
#IBAction func addView(sender: NSButton) {
let customView = CustomView(frame: <some frame>)
configWindow.contentView.addSubview(customView)
// Print the views in the window, see what's been added
for i in configWindow.contentView.subviews {
println(i)
}
}
Edited to add
I've created an example project that you can download at https://bitbucket.org/abizern/so-27874883/get/master.zip
This basically initialises multiple views out of a nib file and adds them randomly to a view.
The Interesting part is:
class CustomView: NSView {
#IBOutlet weak var label: NSTextField!
class func newCustomView() -> CustomView {
let nibName = "CustomView"
// Instantiate an object of this class from the nib file and return it.
// Variables are explicitly unwrapped, since a failure here is a compile time error.
var topLevelObjects: NSArray?
let nib = NSNib(nibNamed: nibName, bundle: NSBundle.mainBundle())!
nib.instantiateWithOwner(nil, topLevelObjects: &topLevelObjects)
var view: CustomView!
for object: AnyObject in topLevelObjects! {
if let obj = object as? CustomView {
view = obj
break
}
}
return view
}
}
Where I create a factory method of the custom class that loads itself from the nib, and then returns the first top level object of the correct class.

Resources