View based NSTableView EXC_BAD_ACCESS on Lion with ARC - cocoa

This is weird. I've got a super simple project to learn NSTableView, and it's set up in my nib, set as a View-based tableView. I've also set the dataSource and delegate to my controller obejct.
When I do this, however, and run, I get an EXC_BAD_ACCESS, with the trace starting in my main function and the rest of the stack is internal to Cocoa (so not my code).
There's really nothing fancy going on, other than this project is using ARC (it's a new project, so this was the default).
I also tried using the Analyzer to make sure I wasn't improperly doing memory managment anywhere and there were no issues with it.
I don't get the crash if I don't set the dataSource/delegate, but obviously this is not a very good way to build my app!
Any ideas?
Edit
The delegate and dataSource are both set up in IB. The code is as follows (view-based). It's important to note, I'm getting crashes whether or not this code is present, and it's the same crash in either case:
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return 5;
}
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
NSTextField *cell = [tableView makeViewWithIdentifier:#"MyView" owner:self];
if (nil == cell) {
cell = [[NSTextField alloc] initWithFrame:CGRectZero];
cell.identifier = #"MyView";
}
[cell setStringValue:[NSString stringWithFormat:#"Row %d", row + 1]];
return cell;
}

It's simple!
I had been (somewhat intentionally) trying to leak a variable (because I was too lazy to make an instance variable...writing quick code here), but of course ARC took care of that leak for me, causing the whole thing to blow up.
So, I just needed to make a strong property so the object I was trying to have stick around (which object was acting as my tableView's delegate and dataSource) would not be prematurely released.

Related

Understanding NSPopover with ARC

I'm somewhat puzzled by object lifetimes under ARC. Here’s a scenario which I think is probably common.
1) In response to some event, was load an NSViewController from a nib.
- (IBAction) doIt: (id) sender
{
InfoController *editor=[[InfoController alloc]initWithNibName:#"InfoController" bundle:nil];
[editor show: .... ]
}
2) The InfoController then displays an NSPopover.
3) Sometime later, the user clicks outside the NSPopover. The popover closes itself.
But when does the InfoController get released? For that matter, what's keeping it alive after doIt returns? In my implementation, InfoController is a data source and delegate for controls in its NSPopover, but in general data sources and delegates aren't retained, right?
I realize your question is a bit old now, but I came across it while researching a retain cycle with my NSViewController and NSPopover:
The NSPopover contentViewController property is retaining your NSViewController. That is why you can show the popover like you (and I) do as a response to an action, without another object retaining it. What I found though, is that to properly release the NSViewController under ARC, the contentViewController should be set to nil when the popover is closed. This is in my NSViewController subclass:
- (void)popoverDidClose:(NSNotification *)notification
{
self.popover.contentViewController = nil;
}

UITableView becomes unresponsive

UITableViewCell becomes unresponsive this was a very different problem with a very different solution.
My tableView which is a subView in a UIViewController initially works fine and I can select individual rows in the table. However, I have created my own popup when a row is selected (the popup is a UIView) that appears towards the bottom of the screen. As this pops-up I also create a another UIView which covers the screen behind the popup and it makes the background go dim. The third thing that happens is that i create a UITapGestureRecogniser to keep track of the user's taps, and if they tap outside the UIView then the two UIViews and the TapGestureRecogniser are removed and call the deselectRowAtIndex... method.
However, it is at this point that I cannot use the tableView, as i want to be able to select a different string within the tableView and the popup to appear again (the popup will eventually contain links that will enable the user to move to different viewControllers).
I have tried to reload the data, remove the tableview and replace it, edit the didSelectRowAtIndex, remove the deselectRowAtIndex method, however nothing I tried seems to work and i can't find anything on stackoverflow as my question seems to be quite specific (although I apologise if there is something out there).
I'll add a few parts of my code in, however, I'm not sure where the problem is and I may not have copied the right part in.
The remove overhead is the selector method from the tapGesture
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if(_popOverView == nil)
{
_popOverView = [[UIView alloc]initWithFrame:CGRectMake(20, 200, 280, 150)];
_popOverView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"wood.jpeg"]];
}
if(_mask == nil)
{
_mask = [[UIView alloc] initWithFrame:self.view.frame];
[_mask setBackgroundColor:[UIColor colorWithWhite:0.0 alpha:0.78]];
}
if (_tapDetector == nil)
{
_tapDetector= [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(removeOverHead:)];
}
[self.view addSubview:_mask];
[self.view addSubview:_popOverView];
[self.view addGestureRecognizer:_tapDetector];
}
-(void) removeOverHead:(UITapGestureRecognizer*) sender
{
CGPoint locationOfTap = [_tapDetector locationInView:self.view];
if (locationOfTap.y < (200 + 150) && locationOfTap.y > 200 && locationOfTap.x > 20 && locationOfTap.x < (20 + 280) ) {
NSLog(#"%f,%f",[_tapDetector locationInView:self.view].x,[_tapDetector locationInView:self.view].y);
}
else
{
[_mask removeFromSuperview];
[_popOverView removeFromSuperview];
[_tapDetector removeTarget:self action:#selector(removeOverHead:)];
/*this idea doesn't work :(
[self.tableView removeFromSuperview];
[self.view addSubview:_tableView];*/
}
}
I really hope the answer is in here and is very simple, and thank you in advance for taking the time to read this.
Solved it! Sorry for wasting your time. It was the wrong remove method for the gestureRecogniser. I replaced
[_tapDetector removeTarget:self action:#selector(removeOverHead:)]
with
[self.view removeGestureRecognizer:_tapDetector]
as the UIGestureRecogniser was lingering and obstructing the tableView!!
If you stick a breakpoint or NSLog() inside the else block of that remove method, do you get inside it?
It sounds like your if statement might be off. You should use CGRectContainsPoint(). However if I understand correctly, you're attempting to dismiss everything when the user taps the dimming background view. You could make this view a button or you could compare the touch's view pointer to the pointer to the background view.

NSTableView: only numberOfRowsInTableView: datasource is called

I met a problem about the NSTableView, and it is like:
one datasource of tableview is called:
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
return [self.dataArray count];
}
But this one is never called:
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
// the code
}
I have checked the the array really had data, and also checked the
[NSTableView reloadData] was called in the main thread.
SO I'm totally lost about this weird problem.
Would any one give some tips?
thanks!
I moved the initial code from awakeFromNib to the init, and it called correctly. But I still don't know the real reason. I guess it matters with the life-cycle of the view because the class which has the tableview is a subclass of NSObject and not of NSViewController.

view-based NSTableView's views from XIB?

Is it possible to have a separate XIB file for the NSTableCellView of a view-based NSTableView? Maybe with the help of a NSViewController?
Yes, it seems to be possible.
From Apple's documentation:
In order to function, a programmatically implemented view-based table must implement the following:
...
The - (NSView *)tableView:viewForTableColumn:row: method that is defined by the NSTableViewDelegate Protocol. This method both provides the table with the view to display as the cell for the specific column and row, as well as populates that cell with the appropriate data.
This way you can have an object of the class NSView (or any subclass) and give it back, after you properly filled it with data. Where you get this object from, is not of interest. As far as I know, it would be possible to do the following, for example:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Assume you have a XIB called View.xib
[NSBundle loadNibNamed:#"View" owner:self];
// And you have an IBOutlet to your NSTableView (that's view based) called tView
[tView reloadData];
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return 20;
}
- (NSView *)tableView:(NSTableView *)tableView
viewForTableColumn:(NSTableColumn *)tableColumn
row:(NSInteger)row {
// Assume your class has an IBOutlet called contentOfTableView,
// your class is File's Owner of the View.xib and you connected the outlet.
return contentOfTableView;
}
Hope it works. I just threw it together having a rough idea in mind. Good luck!

How to bind NSButton enabled state to a composed condition

This is my situation in Xcode Interface Builder:
There is also an NSArrayController in entity mode which controls the content of the NSTableView. I want to enable the 'Create' button when the NSTableView is empty (as controlled by the NSSearchField) AND when the text in the NSSearchField is not empty. How do I achieve that? Is it possible without programming?
To what KVO compliant values can I bind the 2 enabled conditions of the 'Create' button?
I don't think there's a way to do it entirely in interface builder, but with a small amount of code you can get it working pretty easily. First, make sure your controller (or App Delegate) is set as the delegate of the search field, and that it has IBOutlet connections to the search field, the button and the array controller. Here's how I would implement it:
// This is an arbitrary pointer to indicate which property has changed.
void *kObjectsChangedContext = &kObjectsChangedContext;
- (void)awakeFromNib {
// Register as an observer so we're notified when the objects change, and initially at startup.
[arrayController addObserver:self
forKeyPath:#"arrangedObjects"
options:NSKeyValueObservingOptionInitial
context:kObjectsChangedContext];
}
// This updates the button state (based on your specs)
- (void)updateButton {
BOOL canCreate = (searchField.stringValue.length > 0 &&
0 == [arrayController.arrangedObjects count]);
[createButton setEnabled:canCreate];
}
// This delegate method is called whenever the text changes; Update the button.
- (void)controlTextDidChange:(NSNotification *)obj {
[self updateButton];
}
// Here's where we get our KVO notifications; Update the button.
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (kObjectsChangedContext == context)
[self updateButton];
// It's good practice to pass on any notifications we're not registered for.
else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
If you're new to bindings some of that may look like Greek, hopefully the comments are clear enough.
I'm SOOO late for this, but came up with another method and just tested it in my app. It works, so I'm going to share it for anyone who will find this question in the future.
Basically what you want to do is to create a property WITHOUT a corresponding value in your controller
#property (readonly) BOOL enableProperty;
This means that there's actually no
BOOL enableProperty;
defined in the header file, or anywhere
then, rather than synthesize it, just write your own getter, and put there your condition
- (BOOL) enableProperty{
return (condition);
}
Third step: anytime there's the chance that your condition changes, notify it.
- (void) someMethod{
//.... Some code
[self willChangeValueForKey:#"enableProperty"];
[Thisline mightChange:theCondition];
[self didChangeValueForKey:#"enableProperty"];
//.... Some other code
}
fourth step: in IB, bind your control's enabled property to this "fake" property.
Enjoy! ;)
You seems to have a window, so presumably you have a controller object which is set as the File's Owner for the NIB file.
Why not declare a boolean property in this controller class, that returns a value based whatever conditions you want ?
#property(readonly) BOOL canCreate;
That you implement :
-(BOOL)canCreate {
// compute and return the value
}
Be sure to send KVO notifications appropriately when the conditions for the creation change.
The last step is to bind the button's enabled binding on the File's Owner canCreate key.

Resources