NSTableView makeViewWithIdentifier across nibs - cocoa

I have a similar question to Cocoa - View-Based NSTableView, using one cell in multiple tables, amplified by
Apple's own docs for makeViewWithIdentifier:owner:
"Typically identifier is associated with an external NIB in Interface Builder and the table view will automatically instantiate the NIB with the provided owner."
This seems to imply that you should be able to store the NSTableCellView in a separate nib from the nib containing the NSTableView. However, in my experimenting, I have only ever been able to obtain cells which are contained within the tableview I'm calling this on. I.e., if I cut and paste my cell into a new .xib file, the tableview can no longer find it. What am I doing wrong, or is this actually impossible and I am somehow misreading Apple's docs?

Use - (void)registerNib:(NSNib *)nib forIdentifier:(NSString *)identifier to register a nib to be used with a cell identifier.
If it doesn't work you're probably registering the nib after the tableView data has been loaded. Use [tableView reloadData] afterwords to be sure it's not a timing issue.

I just ran into this problem and I think you cannot use makeViewWithIdentifier:owner: when you're using a dedicated Nib to populate View-Based Tables.
The problem has to do with file owners (ie. view controllers). makeViewWithIdentifier:owner: seems intended to be used with "self" as the owner for simple custom views.
Generally if you have a separate nib for the custom view with outlets, you're going to want a separate view controller too. Otherwise, if your custom view has an outlet and the table displays many custom views, which outlet are you referring to from the "self" table view owner?
So in my test, I've got the AppDelegate as the delegate/datasource of the Table View. I have a CellView.xib, and CellViewController.h/.m with outlets to the interface. Then in my tableView:viewForTableColumn:row: delegate method I have this code:
SSCellViewController *vc = [[SSCellViewController alloc] initWithNibName:#"CellView" bundle:nil];
return vc.view;
What you lose is the cell re-use that happens automatically with makeViewWithIdentifier:owner:. To implement that yourself, you'll also likely have to deal with managing the many view controllers you've created.
I might still be missing something, as I'm coming to OS X development after years of only doing iOS work.

Related

How to wire-connect NSTableView/Table View to avoid runtime error "Could not connect action, target class NSObject does not respond to")

In a view-based single-column NSTableView containing a default NSTextField in its Table Cell View, I'm trying to listen to confirmed user edits by connecting the NSTextField's action, within an Interface Builder view of the .xib, to a method in my ViewController for the window. But at run time (during window initialization) I get "Could not connect action, target class NSObject does not respond to -textCellChanged". I don't understand which NSObject is being incorrectly targeted, and I have many other NSViews in the window correctly connected to other outlets and actions in the same WindowController.
I see various other posts with a similar symptom, often also in the context of NSTableView, and have explored the solutions or partial solutions to those other problems in my context without success. Is there any particular magic about wiring Table View Cells in Interface Builder that I am overlooking? Or perhaps phrased differently: how is the target object actually determined at runtime, when the action is simply a class method in the File's Owner (and when does this vary for different controls all wired to the same owner of a common superview)?
Here are some particulars of context:
File Owner is set to a subclass of NSWindowController and Module there correctly inherited to my app target.
Probably relevant: I am not using Storyboards, and the top-level object in my XIB's outline view hierarchy is a Window (NSPanel), rather than an View or View Controller. The NSWindowController only appears within the XIB as the File Owner (not as its own object in Outline View).
In any of the various wiring scenarios I've tried (following), Interface Builder "looks like" the wiring op has succeeded. After wiring, the File Owner's Connections inspector lists the intended connection under Received Actions ("textCellChanged: ... [x] Table View Cell"), along with many other actions connecting components in the NSTableView's superview to other methods in the NSViewController.
Likewise, connecting the NSTextField as an OUTLET in the same NSViewController works with no problem. It's only the action (or target/action?? in IB one only sets "action") that fails.
The File Owner is also the data source and delegate of the NSTableView, and the NSTextField is set to Action:Send on End of Editing and Behavior:Editable. I don't think any of these are relevant to the particular symptom, which is just a failure to connect an action.
The NSWindowController is Swift; I have tried implementing the appropriate action within both the main NSWindowController implementation or in an extension that implements NSTableViewDelegate to no noticeably different effect.
Other posts suggest Xcode bugs in wiring, though in older versions of Xcode (I'm in 10.2). Here are various approaches I've tried, all with similar results:
Ctrl+Drag from Table View Cell icon in IB Outline View to NSWindowController source module, targeting there either an existing #IBAction or permitting Xcode to generate a new Connection (type Action, Object: File's Owner) and with it a new method in the ViewController
Reverse Drag from Source Code "left-column radio circle" next to an #IBAction to the Table View Cell in Outline View of my .xib
Ctrl+Drag from Table View Cell icon (in Outline View) to Placeholder/File's Owner icon, then choosing an appropriate action method from the pop-up list of methods implemented in the view controller.
Possibly some others
Finally, here are some related posts and how they differ:
This sounds like an identical symptom, but in comments OP claims to have fixed the issue by a combination of setting File Owner to the View Controller (done) and by working around blocking XCode bugs (not visible in my context).
This suggests I'm linking to a stale (deleted) method; definitely not the case when I allow Xcode to create the method for me.
This unanswered post suggests the user gave up on the situation as an IB bug and gave up in preference for a non-target/action workaround. I suppose I could pursue listening to notifications on the NSTextField as a similar workaround.
Finally, the accepted answer to a similar symptom here is that the connection to File Owner is incorrect in that case, where File Owner was the NSApplication object rather than the View Controller. In my case, File Owner is the View Controller object that defines these methods, so feels like the correct target.
Any stone unturned here? Thanks in advance for your help.
The file's owner in a xib file is a placeholder object representing the owner of the nib and representing the owner object passed to makeView(withIdentifier:owner:). Pass the owner of the nib (usually the view controller or window controller) to makeView(withIdentifier:owner:).

Designing UICollectionView cells in nib with Interface Builder (without storyboard)

I am trying to design a custom UICollectionViewCell prototype (in Xcode 5.0.2), however Interface Builder doesn't let me add a cell to my UICollectionView while designing a nib. I can set the number of items (cells) and Interface Builder creates and displays cells perfectly if I'm using storyboard, but I can't add a cell to my collection view in a nib. I've tried:
Drag and dropping collection view cell into collection view manually from the object library. (fails: doesn't let me drop the cell anywhere in my view)
Creating my collection view with cells in storyboard and copy-pasting the whole view into nib. (fails: collection view is copied but the cell is gone)
Creating my collection view with cells in storyboard, opening the storyboard as source code, finding my collection view cells, copying the relevant XML, opening my nib as source code, pasting it inside my collection view in XML. (fails: unable to open the nib in Interface Builder, it gives errors. When I remove the cell from source code, it opens again. Do not try this if you don't know what you are doing.)
I've also seen several questions about the same issue:
Is it possible to create prototype cells in Interface Builder without story boards?
Custom Header in UICollectionView with Interface Builder without Storyboard
Prototype Cells in a nib instead of a storyboard
They all point out to doing them programatically and/or using another nib for the cell. I know how to do them, but is there any way to design the collection view cell, inside a collection view inside the same view in a nib, just as in storyboard? Why doesn't Interface Builder let me do that in nib where it allows (and even encourages) perfectly using storyboard?
The simple answer is no, this cannot be done. Think of a storyboard as a collection of XIBs. A XIB simply defines the facets of a particular view so that it can be constructed at runtime.
Regarding collection views and their storyboard implementations, it's important to understand that a storyboard allows for nesting of viewcontrollers and defining collection views with their XIBs because that keeps the fundamental paradigm of storyboards coherent. Since a storyboard is the means of defining the "story" or scene of an application it is only natural that it allows for the declaration of the reusable views for use inside a collection view.
The same cannot be said for XIBs because the fundamental idea behind XIBs is in reusability. This will allow a collection view defined in a XIB to have any cells used with it as long as the controller registers these classes with the collection view. This way you get the benefit of reusability as another controller can use the same XIB and register different cells etc.
So I think it would be far more confusing to allow for the declaration of the supported cells of a collection view inside a XIB since that breaks the single responsibility principle(if it can be called that) that XIBs aspire to.
Your best solution would be to define a custom collection view subclass that registers the relevant cells on instantiation, and then use this class in your XIB.

Global UITableViewCell identifiers, created once in storyboard and used in all tables of the app

I have an application that loads a cell in various table views. I want this specific cell to be the same across all tables view and every time i change something in its design to be populated in all views.
So far I have the cell creation method inside an object, which is call by all table views. If i make changes to that methods all table view are updated.
I have designed the cell in Storyboard and assign it a unique identifier. I want to keep it that way (not design it programmatically). So far I have to copy/paste the cell to all the tables that it is being used. Is there a solution to create a cell in storyboard that can be used in all table views without having to copy/paste.
Unfortunately we can't create a specifically table view cell in story board.
If you want to maintain a single cell instance for all the tables in the application, you need to to create a singleton instance and need to implement all the delegate and data source methods in that class. While setting the delgate and datesource objects to the table view object, you need to give the singleton instance. So that the implementation of the table view cells is single time through out the application and you don't need to copy paste all these methods for each table.
You can lay out the cell in its own xib file instead, create a UINib object from the nib, and associate that with the reuse identifier for your particular table.
Your nib must have a UITableViewCell (or subclass) as its top level object.
In viewDidLoad, you'd have something like:
UINib *sharedCell = [UINib nibWithName:#"SharedCell" bundle:nil];
[self.tableView registerNib:cellNib forReuseIdentifier:#"SharedCell"];
This way you can use the same design in several storyboards.

Sharing an NSArrayController between multiple views in separate NIB files

First, some background: I am trying to implement a master-detail interface in Cocoa (for OS X). That is, I have a window with two NSTableViews that display two different types of objects. For this question, let's say they are warehouses and packages (to pick an example that is analogous to my actual problem.) Selecting a row in the first table view (on a warehouse) will display a list of packages which belong to that warehouse in the second table view. For the model part, I currently have an NSMutableArray called warehouses of warehouse objects, and each warehouse object has an NSArray of package objects. One thing to note is that the warehouses variable is modified after the NIB files are loaded, so the NSArrayController has to be notified.
Now, I've tried to organize it so that the "master" is in its own view object, and the "detail" is in its own view object. This means that there are three NIBs: a WarehousesView NIB, WarehouseDetailView NIB, and a MainWindow NIB.
The WarehousesView NIB contains an instance of a WarehousesViewController (subclassed from NSViewController) and the view itself.
The WarehouseDetailView NIB contains an instance of WarehouseDetailViewController and the view itself.
The MainWindow NIB contains the main window, an instance of MainWindowController, and an instance of both WarehousesView and WarehouseDetailView. The window itself contains an NSSplitView, and the views of the split view are connected to the corresponding view instances in the NIB file.
That brings me to the first half of my question:
1) Is this a good way of splitting up the application views for a Cocoa application? To me it makes sense, because at a later point more details about the warehouse besides a list of its package inventory might be added to the WarehouseDetailView.
It's an important question, because everything works just fine if I skip creating views, putting all controls in the window directly and put everything else, including NSArrayController instances corresponding to Warehouses and Packages, into the same NIB file. I don't need to ask the second half of the question if I shouldn't even be doing it this way.
The second half of the question is basically:
2) Where should I place the NSArrayControllers corresponding to Warehouses and Packages if I split it up as described above so that the master-detail interface still works? Currently I am using Cocoa bindings, so somehow the content array of the Warehouse NSArrayController needs to bind to my warehouses array, and the content array of the Packages NSArrayController needs to bind to the selection of the Warehouse NSArrayController
I've tried a few things, and I couldn't get anything to work completely. Specifically, I've tried putting the NSArrayController for Warehouses into the WarehousesView NIB and the NSArrayController for Packages into the WarehouseDetailView NIB. The problem with this approach is that I cannot figure out a way to bind the Package NSArrayController to the selection of the Warehouse NSArrayController. The other thing I've tried is (1) putting both NSArrayControllers into the MainWindow NIB, (2) connecting those NSArrayControllers to IBOutlets in the MainWindowController, then (3) passing those variables to their respective view controllers via their constructors, (4) exposing them as properties in the view controllers via KVC, and (5) binding the necessary table columns in a view to the array controller through the File's Owner. The result was that nothing appeared, but there were no errors either. If one of these approaches is the preferred way to do it, I can give more details to help see if I am doing it incorrectly.
Thanks in advance!
Edit: I did look at this related question, and they seemed to be using separate instances of NSArrayControllers for each NIB file if I understood it correctly, and that didn't seem to make sense from a design point of view, but perhaps I am wrong?
Part 1: You can certainly do this. I'd say it's a matter of preference. Personally, then, if the views were going to be displayed simultaneously in a window, I would keep them in the same nib.* Modularity is also a good thing, though.
Part 2: You can put the array controllers wherever you like, really. The only thing you need to worry about is getting each object the references that it needs to the information you want it to have. If you want my 2ยข, I'd say put each in the nib with the view its contents will be displayed in. That'll make your detail view setup more difficult, but it continues the modularity you seem to be going for.
You have to remember that every object in the nib is a real instance. The nib allocates and inits them for you; if you put a MyClass object in one nib, and a MyClass object in another nib, those are two different objects. This is sometimes a tricky thing about nibs: it's really convenient to have instances automatically created for you, but it also means some fiddling around with references when you want to do things across nibs.
It sounds like you put instances of WarehouseView and WarehouseDetailView into both your individual nibs and MainMenu.nib and expected them to be the same objects. It won't be so. You have to link objects in the nibs to objects that they already know about. You'll have to work this out for your particular situation.
I don't know where your model is stored, or how you're getting the nib loaded. Whichever object it doing that loading, though, will likely be your link between the individual nib and the rest of the app. This is what the File's Owner proxy object in nibs is for -- it gives you a place to hook up objects in the nib to code that they wouldn't otherwise know about.
*: If you find it easier to layout the views if they are not enclosed in the split view in IB, you could set them up on their own: put the custom view objects in the MainMenu.xib window and you can open each view in its own IB window (though it will not be in a window in the app). Then set the split view's subviews in something's awakeFromNib.

Why do UIViewControllers have xib files and UIViews do not?

When I create a new UIViewController in xcode, it offers to make me an associated nib for the interface. However, when I create a UIView, it does not. If my understanding of MVC is correct, views should really be the parts that contain the interface elements (i.e. the nib) while view controllers are the parts that hook the functionality to the views they control.
I'm sure I'll be able to get it working together either way, so this is more of an exploratory question.
This seems like a case where I'm missing some fundamental understanding of how these two parts should be used. What am I missing?
Because UIView is usually not used in such way.
However How do I associate a nib (.xib) file with a UIView?
The answer I eventually got that satisfied my interest was roughly this:
The job of a view controller is to manage a view hierarchy. Interface Builder is an excellent tool for creating view hierarchies. Xcode offers to create a .xib when you create a new view controller because chances are high that you'll want to load the controllers' views from a .xib.
.xib files aren't necessarily meant to hold every UIView (or subclass) that goes into the view, just a general outline of views that don't change during the life of the view. The other UIViews are much easier to create programmatically since they change often.
I had a similar confusion. Basically (according to the MVC) the View is contained inside the Controller. In the iPhone this means that UIViewController has a property called 'view' which is a UIView instance.
A XIB file (and this is not mentioned often) is a serialised UIView instance. It is roughly an XML sub format which represents a UIView with all its subsequent views. So when you create a UIViewController, a UIView is created in the form of a XIB and bounded to that controller.
A new UIView therefore does not have a XIB because they are essentially the same thing...

Resources