NSTableView loads data, but cannot reload - macos

I've got two NSTableViews set up, being populated by one nstableviewdatasource and delegate. Both tableviews receive data when first loaded without issue, however they refuse to reload data. Both tableview datasource and delegates are connected to my tableviewcontroller class object in the xib.
I've confirmed that my method, which sets new data to the array I use to populate the tableviews, actually receives the changed data. This method is the only way I populate the array that the datasource delegate methods use. It is being reloaded from the main thread:
myArray = [classThatHoldsNewVariables.array mutableCopy];
[self.myTableView reloadData];
-numberOfRowsInTableView does not get called with reloadData. It only gets called on app launch.
I've tried manually setting the datasource and delegate methods in code, without any success. I've tried so many different ways of getting reloadData to work, and nothing happens.
Help :(
EDIT:
I fixed the problem. It was an instance issue, where I was alloc init-ing my nstableviewdatasource from another class to reload the data. Init does not load the instance used by the nib, so it's reloading another instance of my tableview, which isn't shown to the user. I solved this by implementing nsnotifications in my tableviewcontroller class instead.

Sounds like myTableView is nil, thus sending it the reloadData method isn't doing anything.
If it's an outlet, make sure it's connected in the xib. If not, note that a UITableViewController's table view is, by default, referenced by the tableView property, not myTableView.

I fixed the problem. It was an instance issue, where I was alloc init-ing my nstableviewdatasource from another class to reload the data. Init does not load the instance used by the nib, so it's reloading another instance of my tableview, which isn't shown to the user. I solved this by implementing nsnotifications in my tableviewcontroller class instead.

Related

NSArrayController returns null

I have an NSArrayController bound to CoreData in my application. It is also bound to a TableView that displays the data. Two buttons are bound to the ArrayController that add and remove lines. All of this is working as expected. I can add, edit, save, and remove CoreData Entries.
There is a section of my app that is to accept drag and drop operations from files (working). It takes the data from the files, looks for various information, and is to insert this information into the Core Data database via the NSArray Controller.
I have added the class handling the parsing/adding of the file to the database as an object in IB. I created an IBOutlet for the array controller in the class, and bound the controller to the class' referencing outlet.
If I add a button to the interface to directly call the method that adds a custom record to the database, everything works. If the method is called via the drag and drop operation, nothing works, even logging a simple [arrayController className] returns null (though returns NSArrayController as expected when the method is called from the button click).
The only difference I can see is that when accessed through the button click, the method is called directly, while the other way passes through my drag and drop class before loading the parsing class, but I'm completely stuck on how to remedy this situation. I'll be happy to provide code, just not sure which code you'll need.
Any help is appreciated. Thanks!
==================
UPDATE
turns out I was connecting the IBOutlet to a class (a subclass of a view) object in IB instead of to the view itself handling the drops. Connecting these up made things work. Well, not work, I have other issues to iron out now, but the Array controller is now instantiated.
Moved from comment to answer: The array controller you are trying to add stuff is not instantiated. I assume you are not referring to your original NSArrayControllerinstance but maybe a new created one? Probably a problem of communication between your class instances.
Debugging this should be straightforward ... using the debugger. Set a few breakpoints (one at each action the button(s) call, and one at each point where your class instances are meant to talk to each other (your importer and your main controller)). Run, test, step through the code when the debugger breaks at each breakpoint.
My guess: An outlet is not hooked up (is nil) in IB or is not yet reconnected at runtime (see -awakeFromNib and make sure you're not trying to touch an outlet or action that hasn't been fully reconnected from the nib at runtime by the time you're trying to use it).
Something’s not hooked up right, BUT you don’t want to do it this way anyways. There’s no advantage to inserting via an NSArrayController. Just create new objects with NSEntityDescriptions:
+ (id)insertNewObjectForEntityForName:(NSString *)entityName inManagedObjectContext:(NSManagedObjectContext *)context;
And you’re done. If your NSArrayController is hooked up correctly it’ll auto-fetch the new objects at the end of the event so the user will see them “immediately.”

initWithNibName either called twice or wrong xib loaded

I'm programming a Cocoa application and want the application to work as a kind of Wizard. So in the main window I have a Custom View that interacts with the user and changes from a sign in to a device activation screen as they step through the stages of the wizard. I have currently overridden the WizardViewController's awakeFromNib method:
- (void)awakeFromNib{
//If no redirect request save, add first view: ID Login
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *tokenRequest = [defaults objectForKey:#"redirectRequestToken"];
if (!tokenRequest){
SignInWithIDViewController *signInViewController = [[SignInWithIDViewController alloc] initWithNibName:#"SignInWithIDViewController" bundle:nil];
[wizardView addSubview:[signInViewController view]];
} else {
NSLog(#"Have already logged in.");
}
}
As is, initWithNibName in SignInIDViewController gets called twice, once explicitly by me, and again when the view is loaded (presumably through loadView). However, if I simply call init then initWithNib name is only called once, but the wrong xib file is loaded (of the DeviceActivationViewController class). I can't seem to figure out what I'm doing wrong, because the signInViewController should not be init twice, but I need the proper xib file in IB to display.
The only other method I have in this class currently that is not a user interface IBAction is the generated initWithNibName method plus an added NSLog statement.
I think that creating the objects in IB (the blue cubes), and instantiating them in code is the problem. If you've created objects for them in IB, then they will be instantiated in awakeFromNib, you shouldn't also call alloc init on them in code -- that will create a new instance.
I don't have a lot of experience with using view controllers in OSX, but it seems that you can't connect IBActions to the view controller (as file's owner). The way I made it work, was to subclass the custom view (that's created for you when you add a view controller), change the class of that view to your new subclass, and put the action methods in that class. It seems like this should be something that would be handled by the view controller, but I think it not working has something to do with the view controller not being in the responder chain in OSX (whereas I think it is in iOS).
After Edit: After a detour into memory management problems, I think I found the best way to do this. You can, and probably should (to comply with Apple's MVC paradigm) put the button methods in the view controller class rather than in the view as I said above. You actually can connect the IBActions to the view controller (as File's Owner), you just need to make sure that the view controller is retained when you instantiate it in code. To do this, you need to make signInViewController a property in whatever class you're instantiating the SignInViewController class in, and use "retain" in the property declaration. Then you don't need to (and shouldn't) create any of the blue cubes in IB.

textShouldEndEditing does not get called in NSTableView

When a user adds a new managed object, it shows up in a table, which scrolls down to the new entry, and the name of the new object (a default value) goes into editing mode.
I need to check if the name of the new object is unique in the datastore, so I can't use a formatter for this. I think the perfect moment where I should validate this, is whenever the user tries to commit the entry's name value, using textShouldEndEditing:.
I subclassed NSTableView and overrid following methods, just to be able to check in the log if they get called.
- (BOOL)textShouldEndEditing:(NSText *)textObject {
NSLog(#"textSHOULDendEditing fired in MyTableView");
return [super textShouldEndEditing:textObject];
}
- (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor {
NSLog(#"control:textShouldEndEditing fired in MyTableView");
return YES;
}
- (void)textDidEndEditing:(NSNotification *)aNotification {
NSLog(#"textDIDEndEditing fired in MyTableView");
}
textDidEndEditing: gets called fine, but textShouldEndEditing: does not.
In the NSTableView Class Reference, under Text Delegate Methods, both methods textShouldEndEditing: and textDidEndEditing: are listed. Someone please explain why one gets called and the other doesn't.
I think the NSTableView acts as the delegate for an NSTextField that gets instantiated as a black box delegate for the NSTextFieldCell. So what is referred to as delegate methods in the NSTableView Class Reference, actually implement the text manipulating methods for the NSTextField object.
I tried to declare the NSTextFieldCell as an outlet in my NSTableView. I also tried to declare several protocols in the NSTableView.
#import <AppKit/AppKit.h>
#import <Cocoa/Cocoa.h>
#interface MyTableView : NSTableView <NSTextDelegate, NSTextFieldDelegate, NSControlTextEditingDelegate, NSTableViewDelegate, NSTableViewDataSource> {
}
#end
Don't laugh, I even tried to declare my table view as its own delegate :P
After banging my head one entire day on this issue without finding any conclusive answer in Apple documentation, I decided to share the solution I've found in case somebody else struggles with the same problem.
According to the documentation, as the original poster mentioned, the methods control:textShouldBeginEditing and control:textShouldEndEditing of NSControlTextEditingDelegate should be called directly on the delegate:
This message is sent by the control directly to its delegate object.
Furthermore, a Technical Q&A was issued by Apple with the title Detecting the start and end edit sessions of a cell in NSTableView where it's clearly stated the following:
A: How do I detect start and end edit sessions of a cell in NSTableView?
In order to detect when a user is about to start and end an edit session of a cell in NSTableView, you need to be set as the delegate of that table and implement the following NSControl delegate methods:
- (BOOL)control:(NSControl *)control textShouldBeginEditing:(NSText *)fieldEditor;
- (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor;
The table forwards the delegate message it is getting from the text view on to your delegate object using the control:textShouldEndEditing: method. This way your delegate can be informed of which control the text view field editor is acting on its behalf.
I found nothing in Apple's documentation stating anything different and if someone does, a documentation pointer would really be appreciated.
In fact, this appears to be true if a cell-based NSTableView is being used. But as soon as you change the table to a view-based table, the delegate method is not called any longer on the table delegate object.
A Solution
However, some heuristic tests I performed showed that those delegate methods get called on a view-based table delegate if (and as far as I know: and only if):
The table delegate is set.
The delegate of the editable control is set.
If you remove either delegate, the methods of the NSControlTextEditingDelegate protocol will not be called.
What's unexpected according to the (only) documentation is setting the delegate of the editable control. On the other hand setting the delegate object to receive delegate notifications sounds rather intuitive to me, and that's why I tried in the first place. But there's a catch! The curious thing, though, is that that's not sufficient. If the table delegate is removed, the NSControlTextEditingDelegate methods will not be called even if the delegate of the editable control is set (which is the weirdest thing to me).
Hope this helps somebody else not to lose time on this issue.
in your question you mention the insertion of a "managed object" and that was the problem. It seems that you are using a view based table, but the textShouldEndEditing: method only gets called for cell based tables.
I overrid -(void)awakeFromInsert; in the (subclassed) managed object, to construct a unique default value for the name-property.
Also, I ended up not overriding the -(BOOL)textShouldEndEditing: method in the table view. Instead, I check if a newly entered name-property is unique in the (subclassed) managed object's -(BOOL)validate<Key>:error:.
Together, the above two strategies result in unique name-properties in all managed objects.
Maybe I could have forced the NSTextFieldCell to go into editing mode, resulting in -(BOOL)textShouldEndEditing: to get called every time.
Some remarks though:
It seems -(BOOL)textShouldEndEditing: returns NO when the -(BOOL)validate<Key>:error: returns NO.
Both -(BOOL)textShouldEndEditing: and -(BOOL)validate<Key>:error: methods are called only when the user actually makes changes to the property.

View is null in viewDidLoad

I created UITabBarController with each tab contain UINavigationController and set rootviewcontroller in this UINavigationController, all of this is done in interface builder. In viewDidLoad I try to get frame size from view, but when I reference view it return null. Have anyone experienced this before.
All IBoutlet are properly connected.
viewDidLoad returning a nil view if the view is connected in your .xib could mean:
For programmatic initialisation (custom controllers):
You forgot to call initWithNibName:bundle: on the view controller class altogether (you may have called init or something instead).
You've overridden the loadView method in your view controller class, but it doesn't set the view.
For all controllers:
An outlet connection hasn't been made correctly.
You have accidentally released the view or view controller before showing it.
The nibName parameter was not properly specified when initialising the view (the nib could not be found or one without a view was found.. though this should also throw an exception).
There wasn't enough memory to allocate the view (the app would likely have been terminated by that point though).
I'd recommend you try doing frame size calculations in viewWillAppear: instead and see if the view is still nil at that point.

Call IBAction in different class

My document based application has a window with a tableview. The tableview has a datasource which points to a class of type NSObject (called HopBill) which includes a NSMutableArray (aHopBill) and the needed tableview methods. So far so good.
For adding rows to the tableview I've added a sheet which is controlled from a NSWindowController (called HopBillSheetController). When pressing the OK button in the sheet. I actually need to do two IBActions (which is not possible): Add the row to the array of the tableview and close the sheet. I can connect the OK button in the sheet to the NSWindowController (to close the sheet) or connect it to the NSObject (to add the row to the array). But I want both :-)
Is it possible to call the IBAction in the NSWindowController from the NSObject? Or is there another way to do this?
I'm quite a beginner to Cocao and Objective-C, so please be gentle :-)
If your sheet is a nib/xib with an NSPanel, the call to close it is simply [panel close]; Assuming your window controller has a property for the panel, you can put the close code at the end of its row-adding IBAction. Or you could have the IBAction itself call another method if you prefer.
If your panel is running modal, you might need to stopModal too. (That's what's needed if everything stays frozen after the panel closes; otherwise never mind.)
Assuming hopBill, your data source, is a property of the window controller, any IBAction you write in the window controller also has access to hopBill; it can do everything you need.
So add a single IBAction to the window controller and connect the panel's OK button to it. That ought to work.
As for calling an IBAction from somewhere other than a control in a nib, yes, you can do that. Use a reference to the control as the sender arg, or nil if the IBAction doesn't use the sender arg.
You could also create your panel programmatically, or use NSAlert. But it sounds like your current setup is simpler -- and therefore better.
Take a look at this h file for an app controller: Apple's ClockControl example
The NSMutableArray *appointments property is the actual data source that will be used by the NSTableViewDataSource protocol methods. The IBAction "addAppointment" can access "appointments" directly: [self.appointments addObject:whatever atIndex:whatever];
The ClockControl example could be modified to use HopBill. You would import its declarations up top: #import "HopBill.h" And then instead of the "appointments" property, it would declare HopBill *hopBill; And "addApointment" would access HopBill's mutable array (aHopBill) like this: [self.hopBill.aHopBill addObject:whatever atIndex:whatever];
Why you can’t send messages to hopBill:
First, because although you declare it, you never initialize it. You have:
HopBill *hopBill;
[self.hopBill.aHopBill addObject: bHopAdditionAtInit];
It should be:
HopBill *hopBill = [[HopBill alloc] init];
[hopBill.aHopBill addObject: bHopAdditionAtInit]; // “self” won’t work here
Second, you’re declaring it inside an IBAction method, (doneHopBillSheet:), so it’s a local variable, accessible only within that method. If HopBill is holding your table’s data source cache, it should be a property of the controller which implements the NSTableViewDataSourceProtocol methods.
In your HopBill interface, you declare the aHopBill array to be a property, and you initialize it in HopBill’s init method (you should also release it in HopBill’s dealloc method). You need to do the same thing for the controller — it should have an instance of HopBill as a property, and that instance should be initialized in the controller’s init method.
If you want HopBillController to manage the tableview, its interface declaration should look like this:
#interface HopBillSheetController : NSWindowController <NSTableViewDelegate, NSTableViewDataSource> {
…
}
And, then, of course, you have to implement the relevant NSTableViewDelegate and NSTableViewDataSource methods.
Also, the controller must have an IBOutlet property for the tableview itself, and in the controller’s awakeFromNib method, it has to assign itself as delegate and datasource:
[self.tableview setDelegate:self];
[self.tableview setDataSource:self];
(The self-dot syntax assumes you’ve set up #property and #synthesize code for tableview.)
The IBAction method that adds items to your table must be in that controller class, or in a class that has a property which is an instance of the controller class. Then the IBAction method will have access to the aHopBill array and can add the new object to the array, after which it will call [tableView reloadData], which will in turn trigger the tableview protocol methods and update the table.
Now, that means that the xib containing the tableview has to have the controller as its file’s owner. Since you’re using NSDocument, I suspect that, instead, you would put the tableview outlet in the NSDocument subclass. And you would give that doc subclass a property which is an instance of the controller. The IBAction methods would also be in the doc subclass, and so they would have access to the controller and its HopBill property. Or maybe you would simply make the doc subclass the controller, rather than using the separate HopBillSheetController class. I’m not sure about the NSDocument stuff. But, remember, the IBAction method can itself call other methods, as long as it has access to instances of the classes in which those methods are declared.
Apple has an example using both the tableview delegate and datasource protocol methods. Go to this link and download the sample code: tableview example
It looks like a nice app. Good luck.

Resources