I've got a View-based NSTableView which is configured in IB to be View based. Its column identifier is set, too. As are its dataSource and delegate set, to the same object, which claims to conform to both in the public header. I've verified these are set after the tableView awakes from its nib.
As per the docs, I've implemented -numberOfSectionsInTableView: and it gets called and returns a non-zero number.
However, -tableView:viewForTableColumn:row:, much to my chagrin, does not, and I can't figure out why not.
Does anyone know why this might happen? I'm running on Mountain Lion and my deployment target is also set to be Mountain Lion.
It' simple! The problem was, even though the tableview was added to my view hierarchy, it was clipped so it couldn't be seen. NSTableView must do some checks to see if it's actually on screen and then only request cell views for visible rows.
Because the whole tableview was essentially "off-screen", it wouldn't request any rows.
My NSTableView is added by code instead of IB, I solved it by adding:
[tableView addTableColumn:[[NSTableColumn alloc]initWithIdentifier:#"columnIdentifier"]]
Related
I'm updating an existing project which has worked fine for years to High Sierra. The view loads correctly and looks as it always has:
Then, after I open a popup and close it again, the view moves things around, flips things, and generally looks very crazy:
Notice the Info title drops to the bottom, the left side text items reverse order, and the date and dropdown text flip. Mouse interaction seems very scrambled as well when this happens.
I'm not sure where to even start with solving this one, anyone have any ideas?
This is using xcode 9 beta 5 and High Sierra Beta 6.
Update: This is also in xcode 9 GM and High Sierra GM Seed
Update
I have determined this happens when I call reloadData on the NSTableView that this view is within. So the question now seems to be how to refresh the table data without it going wonky.
It seems that reloadData and also reloadDataForRowIndexes:columnIndexes: both produce odd behavior in High Sierra. My way around this is as follows:
-(void)refreshAllCells {
NSMutableIndexSet* rowIndexes = [[NSMutableIndexSet alloc] init];
if (self.myTable.numberOfRows != 0)
[rowIndexes addIndexesInRange:NSMakeRange(0, (self.myTable.numberOfRows))];
[self.myTable removeRowsAtIndexes:rowIndexes withAnimation:NO];
[self.myTable insertRowsAtIndexes:rowIndexes withAnimation:NO];
}
If there is a better answer I would still love to see it.
For me, adding wantsUpdateLayer in my subclass fixed the problem.
override open var wantsUpdateLayer: Bool {
return true
}
In IB, tick the checkbox to use CALayers on your tableView's views. I toggled them on for all the view's in the tableView view hierarchy and all the flipping stopped!
Can't post an image as I don't have enough reputation but this link shows the IB checkbox:
I've encountered this bug as well and while implementing another answer I found the real reason for the bug due to an exception that now got raised:
In my case, reloadData was called again while reloadData was still being executed. This caused the rendering issues.
The "loop" was happening as a side-effect of calling tableView.makeView(withIdentifier: identifier, owner: self) in tableView(_:, viewFor:, row:). The owner: self caused awakeFromNib() of the controller getting called again which in turn triggered the reloadData(). In my setup, I was not (yet) using separate XIBs for the table cells but was using the cells that were setup inside the table view as visible Interface Builder.
So the solution was to change tableView.makeView(withIdentifier: identifier, owner: self) to tableView.makeView(withIdentifier: identifier, owner: nil) and also use separate XIBs for the table cells.
I'm seeing this as well in 10.13, when 10.12 had worked just fine. I was able to work around this problem by, oddly enough, re-adding the cell view's subviews in its [layout]:
- (void)layout{
if([self needsLayout]){
for(NSView* v in [[self subviews] copy]){
[self addSubview:v];
}
}
[super layout];
}
I experienced this as well. Further testing revealed that the contents were being drawn WHILE the tableview Data source was being updated. By eliminating a spurious update, I was able to eliminate the issue.
I reload the data in the draw method, however I have moved the data update code outside of that method. Not sure if this helps the OP, but it worked for me.
All of a sudden I received several mails from customers who complained about all table texts being shown vertically flipped. All of them use macOS Mojave.
After a lot of hacking, and trying the solutions detailed above (to no avail alas), I decided to disable the autoSave property of the NSTableView:
//Swift.print("Now setting autosaveName of primaryView to \(self.className).primary")
//primaryTableView.autosaveName = "\(self.className).primary"
//primaryTableView.autosaveTableColumns = true
Then the problem was instantly gone. But now my customers weren't able anymore to change and save the column widths and order. So, instead of doing it programatically with tableView.autosaveName = "autosavename"; I now set up the autoSave property in the Table view properties, and also checked 'Column information' in the Attributes Inspector (while showing xib file). And then it works just fine..
I use the currently latest version of Xcode (10.1 10B61), and the problems only surfaced when using macOS Mojava 10.14.0 - 10.14.2 . High Sierra and Sierra gave no trouble. Whether I compiled the project in High Sierra or Mojave made no difference.
So, I hope this helps for other people confronted with this bizarre problem. I think it's a bug in macOS Mojave.
Had the same problem, reproducible on High Sierra and Mojave. The issue was with NSOutlineView autosaveName, other solutions didn't help. However I needed this feature.
Moving adding/reloading data to "viewDidAppear()" instead of "viewDidLoad()" solved the problem.
Edited:
It happens only when:
autosave is present
items get loaded on viewDidLoad before sorting is applied
the same list of items gets re-loaded after sorting is applied
all of the above happens on viewDidLoad
If you try to access tableView.sortingDescriptors (or NSOutlineView) - it automatically applies sorting. If it happens before loading the data - it works ok.
Loading data on viewDidAppear still looks like a better option.
I've been developing on iOS for some time, but am very new to Cocoa development, and something seemingly very simple is stumping me.
I have an NSTableView, hooked up to a subclass of NSWindowController as both datasource and delegate. I have an array of "File" objects (my model class) and want to populate one column of my tableview with file types, and another with timestamps.
The dataSource methods are definitely being called, as verified by setting breakpoints. In fact, I end up with an appropriate number of rows that are selectable...but none of them display anything. I even tried returning an arbitrary string literal in objectValueForTableColumn, for all rows and columns, and still nothing.
I think I'm probably stuck on how tableViews work in iOS, but obviously they are very different here...I am used to configuring and returning a cell myself, but here we just pass AnyObject??? How exactly does the tableView know how to display AnyObject? I'm really struggling with the conceptual understanding here. Appreciate any help.
I'm trying to release some strain on a view-based NSOutlineView for which I changed a single item property and which I initially reloaded just fine using [myOutlineView reloadData].
I tried [myOutlineView reloadItem: myOutlineViewItem] but it never calls - (NSView *)outlineView:(NSOutlineView *)ov viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item and consequently the data is not updated.
-(void)reloadOutlineViewObject
{
//[myOutlineView reloadData]; //Reload data just fines but is ressource-hungry
NSLog(#"%d",[myOutlineView rowForItem:myOutlineViewItem]; //Making sure my object is an item of the outlineView, which it is !
[myOutlineView reloadItem:myOutlineViewItem];
}
Am I missing something here ?
UPDATE
As pointed out in the comments, my outlineView is view-based.
UPDATE 2
Trying out some stuffs made me realized that the object I am reloading is a second-level object (cf object tree) and calling reloadItem:firstLevelObject reloadChildren:YES does work.
Would it be possible that we can only call reloadItem: on first-level object ? That would be highly inefficient in my case (I only have one two level item and plenty of second level) !
nil ->firstLevelA ->secondLevel1
->secondLevel2
->firstLevelB ->secondLevel3
->secondLevel4
Gonna try to subclass NSOutlineView and rewrite reloadItem: in the mean time.
UPDATE 3
I took a look at NSOutlineView in Cocotron to get start and felt that the code I needed to write to overwrite reloadItem would be quiet heavy. Anyone to confirm ?
I encountered this same problem with a view-based outline view, where calling -reloadItem: seems to just not do anything. This definitely seems like a big bug, though the documentation doesn't explicitly say that reloadItem will reacquire the views for that row.
My workaround was to call NSTableView's -reloadDataForRowIndexes:columnIndexes: instead, which seems to work as expected, triggering a call to the -outlineView:viewForTableColumn:item: delegate method for just that item. You can get the row that needs to be reloaded by calling -rowForItem: and passing in the item you want to reload.
This really isn't a bug - it was something I had explicitly designed. My thought was that reloadItem (etc) should just reload the outline view item properties, not the table cell at that item, since it doesn't carry enough specific information on what to reload (such as what specific cell you might want reloaded). I had intended for people to use reloadDataForRowIndexes:columnIndexes: to reload a particular view based tableview cell; we usually don't provide cover methods when the base class can easily do the same thing with just a few lines of code.
However, having said that, I know multiple people have gotten confused about this, and most people expect it to reload the cell too.
Please log a bug requesting Apple to change this.
thanks,
-corbin
Apple seems to have "fixed" it.
WWDC 2016, presentation 203 "What's New in Cocoa" at 30:35 in the video:
"NSOutlineView
Reloads cell views associated with the 'item' when reloadItem() is called"
reloadItem: works only on macOS 10.12.
From release notes:
https://developer.apple.com/library/content/releasenotes/AppKit/RN-AppKit/
NSOutlineView will now reload the cell views associated with ‘item’
when [outlineView reloadItem:] is called. The method simply calls
[outlineView reloadDataForRowIndexes:columnIndexes:] passing the
particular row that is to be reloaded, and all the columns. For
compatibility, this will only work for applications that link against
the 10.12 SDK.
So, if you want to reload row on earlier systems, you should use -reloadDataForRowIndexes:columnIndexes:.
Something like that:
let index = outlineView.row(forItem: obj)
let rowIndex = IndexSet(integer: index)
let cols = IndexSet(0 ... outlineView.numberOfColumns)
outlineView.reloadData(forRowIndexes: rowIndex, columnIndexes: cols)
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.
I have a puzzling problem. Working on a cocoa app in mac os x 10.7.
My app main window contains a split view. In a certain use context in one of the subviews of the split view is loaded a custom view with some labels (nstextfield) and a split view (instantiating a view controller that loads a nib and getting view from that controller). Frame of the custom view is set to split view subview bounds and everything works fine.
Problem is that one of the subviews of the second split view should be loaded (same method: view controller-nib-view-frame/bounds) with a custom view containing a table view and a button, but in this case nothing shows. Everything is done the same way but last custom view is not visible. Any idea?
Thanks
(edit)
this is the code I use to instantiate controller for the view to be added, get the view, and add it as subview to a subview of the split view
- (void)loadSubview {
self.subviewToAddController = [[viewController alloc] initWithNibName:nil bundle:nil];
//nib name is coded in the controller class definition
[[self.subviewToAddController view] setFrame:[self.splitViewContainerSubView bounds]];
//container subView is an outlet
[self.splitViewContainerSubView addSubview:[self.subviewToAddController view]];
}
However I don't think the problem is in this code because if I ask the container subview for its own subviews I can see the new subview is present in the list. It just doesn't show. If I add it as a subview of the split view (a test a just made) or as subview of the subview of the most external split view it is correctly showed too (sorry for the confused explanation, I would need a diagram but in this moment I can't make it)
To elaborate more my doubt (I didn't want to misled so I didn't mention before) can't it be a problem of coordinates, so view is correctly loaded and added as subview but is not visible because hidden by something or showed out of visible area?
(update)
Sorry it took so long to post an update.
After more testing I found out the problem is related to autolayout. No idea what the exact problem is and how to solve it. I ended up turning it off for the nibs the were in troubles and use the old way to set interface objects position and size/resize. Not the best way but for now I can go on.
My best guess is that you didn't set the autoresizing masks of the view properly.