loadNibNamed returns the view, not "the view"? - xcode

I have a small xib, Teste.xib
owned by TesteView
class TesteView: UIView {
#IBOutlet var tf:UITextField!
#IBOutlet var sw:UISwitch!
}
Now, we're gonna load it (and for example stuff it in a stack view).
let t:TesteView = TesteView()
let v = Bundle.main.loadNibNamed("Teste", owner: t, options: nil)?[0] as! UIView
v.heightAnchor.constraint(equalToConstant: 200).isActive = true
stack?.insertArrangedSubview(v, at: 3)
in fact, that's fine.
Everything works.
But note that you insert "v", not "t". "v" is not "the TesteView", it's just some damned view that is floating around.
If you do the following,
t.heightAnchor.constraint(equalToConstant: 200).isActive = true
stack?.insertArrangedSubview(t, at: 3)
it is meaningless, that doesn't work.
But t "is" the view, it's a UIView (indeed, it is a TesteView). It should be the thing you insert.
So you have to use the "two different" things ...
t.tf.text = "WTF???"
// use "t" for that sort of thing
v.heightAnchor.constraint(equalToConstant: 200).isActive = true
v.backgroundColor = UIColor.blue
// but use "v" for that sort of thing
stack?.insertArrangedSubview(v, at: 3)
It seems weird that "t" and "v" are not the same.
(Indeed, should TesteView even have been a subclass of UIView? Maybe it should be something else - just a plain class?? It seems one can not really use it as a view so WTF is it??)
What's the deal on this and/or what is the usual idiom?
NOTE ...
nowadays, there is no reason to ever do this. Just use a small UIViewController. For decades everyone said "Why doesn't apple let you simply load a view controller by id?", well now you can. No problems:
let t = self.storyboard?.instantiateViewController(withIdentifier: "TesteID") as! Teste
t.view.heightAnchor.constraint(equalToConstant: 200).isActive = true
stack?.insertArrangedSubview(t.view, at: 3)
t.tex.text = "WTH???"

This code makes no sense; your use of t is pointless:
let t:TesteView = TesteView()
let v = Bundle.main.loadNibNamed("Teste", owner: t, options: nil)?[0] as! UIView
The object of nominating an owner is to provide some already existing instance with properties that match outlets in the nib. This allows the outlets to be used, and the views to which they lead can immediately be referred to by name.
That is what happens when a view controller / view pair is loaded from a storyboard, and is why storyboards work the way they do. The view nib is loaded with the view controller as owner. So if the view nib has outlet mySwitch and the view controller has outlet property mySwitch, they match up and the term self.mySwitch can be used by the view controller instance to refer to the switch.
You can arrange the same thing yourself when loading a .xib file. But you are not doing that; your code is just deliberately silly.
For example (this is from my book, and you can download and run the example for yourself):
class ViewController: UIViewController {
#IBOutlet var coolview : UIView!
override func viewDidLoad() {
super.viewDidLoad()
Bundle.main.loadNibNamed("View", owner: self)
self.view.addSubview(self.coolview)
}
}
If you have configured View.xib with its File's Owner proxy's class set to ViewController, and if you've then drawn an outlet from the File's Owner to the view and named that outlet coolview, when we load it is matched with the coolview property and the last line works — we can refer to the nib-loaded view as self.coolview.
And notice, please, that we never had to capture the result of loading the nib as an array of views and then refer to element zero of that array, as you do. The view gets slotted right into the name coolview. That is how you load a nib so as to get a useful name or names.

Related

Cannot get NSTableView to populate

In a Swift OSX Cocoa program, I cannot for the life of me get any text to display from a simple two-dimensional array into a two-column NSTableView. The Table View is inside a window in the xib. The dataSource outlet and delegate outlet, and tableView referencing outlet, are all connected to File's Owner. In the controller's .swift code file, I have declared
#IBOutlet weak var tableView: NSTableView!
and that is connected to the xib, so my problem doesn't seem to be the same as [this one]. I want to populate tableView with an array, but like this person I simplified it to populating with a string literal just to try and get any text at all. (The previous poster never seems to have gotten an answer, but he didn't post a code snippet so I will do that.) numberOfRowsInTableView returns 2, as it's currently supposed to. And here's tableView, excluding currently commented-out code:
func tableView(tableView: NSTableView!, objectValueForTableColumn tableColumn: NSTableColumn!, row: Int) -> AnyObject!
{
var newString = ""
newString = "Help!"
return newString
}
As far as I can tell, I should get a 2x2 table with each cell containing the word "Help!" But I don't. The table remains empty. Why?
EDIT in response to comment below: The commenter recommends I drag to Application under Objects, which I think is referring to something like this?
For whatever reason, I don't have that. My document outline for the .xib looks like this:
Maybe that's a clue as to what's wrong?

UICollectionViewCell has no subviews (besides contentView) until it is added to any parent view

Firstly, here is the only question I found that could possibly be related: viewWithTag in UICollectionViewCell returns nil in Swift (until cell is reused)
I'm using Xcode 7.1.1 and actually have another UIcollectionView in my storyboard that works fine. I can find no difference between the one that works and this one.
Here is the code proving my craziness (from inside cellForItemAtIndexPath):
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("VariantCell", forIndexPath: indexPath)
let cellBeforeCount = cell.subviews.count
let contentBeforeCount = cell.contentView.subviews.count
self.view.addSubview(cell)
let cellAfterCount = cell.subviews.count
let contentAfterCount = cell.contentView.subviews.count
print("\(cellBeforeCount) \(contentBeforeCount) \(cellAfterCount) \(contentAfterCount)")`
cellBeforeCount == 1, contentBeforeCount = 0, cellAfterCount == 4, contentAfterCount == 0
Here is the storyboard in XML for this VC:
As you can see, the first view in the <collectionViewCell> is a view whose key is contentView and my labels (you can only see the first) are subviews of it.
Why in the world would none of the subviews exist until after the cell was added to a parent view and why aren't the subviews being added to the contentView? The two have to be related, right?
Ok, chalk it up to not understanding Size Classes. I disabled it and the cell now has subviews as expected:
It makes sense given that the cell has no idea the size of its parent, so it doesn't know which constraints/views to render. This also explains why the cell would have subviews after being added to any parent (and can thus infer its size).
This is an iPad app, so I don't need Size Classes.
I hope this helps someone.

Why does makeViewWithIdentifier:owner: return different types sporadically?

Hello and thanks for reading. I am trying to populate a view based NSTableView from an array of People objects. In my setup i'm using Storyboards with two xib files (one for the main tableview and the other for my custom view).
The call to makeViewWithIdentifier:owner: within the delegate method "tableView viewForTableColumn row" is returning different types for no obvious reason. Sometimes when i compile it returns objects of type "MyOView" (see Console Output 1) which is my custom view class and yet other times when i compile (despite literally no change in my code) it returns NSTextField (see Console Output 2)
Why is this happening?
Console Output 1:
class name of cell is: Saddle.MyOView
class name of cell is: Saddle.MyOView
Console Output 2:
Could not cast value of type 'NSTextField' (0x7fff7e1bbf40) to 'Saddle.MyOView' (0x100017980).
(lldb)
Here is my implementation of tableView viewForTableColumn Row...
func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView? {
let cell = tableView.makeViewWithIdentifier("MyOView", owner: self) as! MyOView
println("class name of cell is: " + cell.className)
let person : Person = allPeopleInRace[row]
cell.itemNumber.stringValue = person.number.description
cell.itemName.stringValue = person.name
return cell
}
In the ViewController in viewDidLoad() i have registered the second nib
let nib = NSNib(nibNamed: "MyOView", bundle: NSBundle.mainBundle())
mainTableView.registerNib(nib!, forIdentifier: "MyOView")
Any help would be appreciated :)
thanks
After some more digging around I realised that I hadn't named my custom cell Xib. Hope this is helpful to someone else.

how to set text in second View Controller

I thought this would be a no brainer, but it turns out I have spent about 5 hours on this now.
I have two ViewControllers and I want to pass a pre formatted NSString to another VC, using a IBAction called putInfo. All this action is responsible for is putting a word into a label on another VC, .
so, in the first ViewController, I implemented the code like this :
- (IBAction)putInfo:(id)sender {
((secondViewController *)self.presentingViewController).ouputLabel.text = #"chicken";
}
I have tried other things like-grabbing a reference to the second VC, instantiating the second view controller, doing that thing where you initialize the second VC WithNibName--all that. ABove is just my latest failure.
This seems like it should be such a no brainer. any suggestions?
If you're using Storyboard, you'll need to use the method prepareForSegue.
But first you need to put a name to your segue: click on it, go to Attribute Inspector and write an Identifier name for the segue.
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([[segue identifier]isEqualToString:#"mySegue"])
{
secondViewController* second = segue.destinationViewController;
second.outputLabel = txtFieldSecondViewController;
}
}
You pass the value of a textField(or some string) to another NSString object of the second view.

Dynamically hiding columns in a NSTableView

I want to dynamically hide/show some of the columns in a NSTableView, based on the data that is going to be displayed - basically, if a column is empty I'd like the column to be hidden. I'm currently populating the table with a controller class as the delegate for the table.
Any ideas? I see that I can set the column hidden in Interface Builder, however there doesn't seem to be a good time to go through the columns and check if they are empty or not, since there doesn't seem to be a method that is called before/after all of the data in the table is populated.
In Mac OS X v10.5 and later, there is the setHidden: selector for NSTableColumn.
This allows columns to be dynamically hidden / shown with the use of identifiers:
NSInteger colIdx;
NSTableColumn* col;
colIdx = [myTable columnWithIdentifier:#"columnIdent"];
col = [myTable.tableColumns objectAtIndex:colIdx];
[col setHidden:YES];
I've done this with bindings, but setting them up programmatically instead of through Interface Builder.
This psuedo-snippet should give you the gist of it:
NSTableColumn *aColumn = [[NSTableColumn alloc] initWithIdentifier:attr];
[aColumn setWidth:DEFAULTCOLWIDTH];
[aColumn setMinWidth:MINCOLWIDTH];
[[aColumn headerCell] setStringValue:columnLabel];
[aColumn bind:#"value"
toObject:arrayController
withKeyPath:keyPath
options:nil];
[tableView addTableColumn:aColumn];
[aColumn release];
Of course you can add formatters and all that stuff also.
It does not work in the Interface Builder. However it works programatically. Here is how I bind a NSTableViewColumn with the identifier "Status" to a key in my NSUserDefaults:
Swift:
tableView.tableColumnWithIdentifier("Status")?.bind("hidden", toObject: NSUserDefaults.standardUserDefaults(), withKeyPath: "TableColumnStatus", options: nil)
Objective-C:
[[self.tableView tableColumnWithIdentifier:#"Status"] bind:#"hidden" toObject:[NSUserDefaults standardUserDefaults] withKeyPath:#"TableColumnStatus" options:nil];
I don't have a complete answer at this time, but look into Bindings. It's generally possible to do all sorts of things with Cocoa Bindings.
There's no Visibility binding for NSTableColumn, but you may be able to set the width to 0.
Then you can bind it to the Null Placeholder, and set this value to 0 - but don't forget to set the other Placeholders to reasonable values.
(As I said, this is just a start, it might need some tweaking).
A NSTable is just the class that paints the table. As you said yourself, you have some class you give the table as delegate and this class feeds the table with the data to display. If you store the table data as NSArray's within your delegate class, it should be easy to find out if one column is empty, isn't it? And NSArray asks your class via delegate method how many columns there are, so when you are asked, why not looking for how many columns you have data and report that number instead of the real number of columns you store internally and then when being asked for providing the data for (column,row), just skip the empty column.
There is no one time all the data is populated. NSTableView does not store data, it dynamically asks for it from its data source (or bound-to objects if you're using bindings). It just draws using data it gets from the data source and ditches it. You shouldn't see the table ask for data for anything that isn't visible, for example.
It sounds like you're using a datasource? When the data changes, it's your responsibility to call -reloadData on the table, which is a bit of a misnomer. It's more like 'invalidate everything'.
That is, you should already know when the data changes. That's the point at which you can compute what columns should be hidden.
#amrox - If I am understanding your suggestion correctly, you're saying that I should bind a value to the hidden property of the NSTableColumns in my table? That seems like it would work, however I don't think that NSTableColumn has a hidden property, since the isHidden and setHidden messages control the visibility of the column - which tells me that this isn't a property, unless I'm missing something (which is quite possible).
I would like to post my solution updated for Swift 4 using Cocoa bindings and the actual isHidden flag without touching the column widths (as you might need to restore the original value afterwards...). Suppose we have a Checkbox to toggle some column visibility (or you can always toggle the hideColumnsFlag variable in the example below in any other way you like):
class ViewController: NSViewController {
// define the boolean binding variable to hide the columns and use its name as keypath
#objc dynamic var hideColumnsFlag = true
// Referring the column(s)
// Method 1: creating IBOutlet(s) for the column(s): just ctrl-drag each column here to add it
#IBOutlet weak var hideableTableColumn: NSTableColumn!
// add as many column outlets as you need...
// or, if you prefer working with columns' string keypaths
// Method 2: use just the table view IBOutlet and its column identifiers (you **must** anyway set the latter identifiers manually via IB for each column)
#IBOutlet weak var theTableView: NSTableView! // this line could be actually removed if using the first method on this example, but in a real case, you will probably need it anyway.
// MARK: View Controller Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
// Method 1
// referring the columns by using the outlets as such:
hideableTableColumn.bind(.hidden, to: self, withKeyPath: "hideColumnsFlag", options: nil)
// repeat for each column outlet.
// Method 2
// or if you need/prefer to use the column identifiers strings then:
// theTableView.tableColumn(withIdentifier: .init("columnName"))?.bind(.hidden, to: self, withKeyPath: "hideColumnsFlag", options: nil)
// repeat for each column identifier you have set.
// obviously use just one method by commenting/uncommenting one or the other.
}
// MARK: Actions
// this is the checkBox action method, just toggling the boolean variable bound to the columns in the viewDidLoad method.
#IBAction func hideColumnsCheckboxAction(_ sender: NSButton) {
hideColumnsFlag = sender.state == .on
}
}
As you may have noticed, there is no way yet to bind the Hidden flag in Interface Builder as on XCode10: you can see the Enabled or Editable bindings, but only programmatically you will have access to the isHidden flag for the column, as it is called in Swift.
As noted in comments, the second method relies on the column identifiers you must manually set either via Interface Builder on the Identity field after selecting the relevant columns or, if you have an array of column names, you can enumerate the table columns and assign the identifiers as well as the bindings instead of repeating similar code lines.
I found a straightforward solution for it.
If you want to hide any column with the Cocoa binding technology:
In your instance of the NSArrayController, create an attribute/parameter/slot/keyed value which will have NSNumber 0 if you want a particular column to be hidden and any value if not.
Bind the table column object's maxWidth parameter to the data slot, described in (1). We will use the maxWidth bound parameter as a message receiver.
Subclass the NSTableColumn:
import Cocoa
class Column: NSTableColumn {
/// Observe the binding messages
override func setValue(_ value: Any?, forKey key: String) {
if key == "maxWidth" && value != nil { // Filters the signal
let w = value as! NSNumber // Explores change
if w == NSNumber(integerLiteral: 0) {
self.isHidden = true
} else {
self.isHidden = false
}
return // No propagation for the value change
}
super.setValue(value, forKey: key) // Propagate the signal
}
}
Change the class of the column to Column.

Resources