How to repaint NSTableView immediately after reloadData - objective-c

I have added a NSTableView and set the delegate and datasource correctly.
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
_items = [[NSMutableArray alloc] init];
[_items addObject:#"ready"];
[self.mainTableView reloadData];
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return [self.items count];
}
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
NSTableCellView *result = [tableView makeViewWithIdentifier:tableColumn.identifier owner:self];
result.textField.stringValue = [self.items objectAtIndex:row];
return result;
}
There is also a NSButton connected with the following code:
- (IBAction)buttonPressed:(id)sender {
for (int i = 1; i <= 10; i++) {
NSString *item = [NSString stringWithFormat:#"%d", i];
[_items replaceObjectAtIndex:0 withObject:item];
[self.mainTableView reloadData];
[NSThread sleepForTimeInterval:0.2f];
}
}
The program ran correctly and shown "ready" in the table view, but when I clicked the button, the content of the table view didn't change immediately. It changed after 2 second, and just show the last number - 10.
What I want is change the content of NSTableViw from 1 to 10 but not directly get the final number. How can I make it repaint immediately after reloadData?

[NSThread sleepForTimeInterval:0.2f];
You're sleeping the thread and wondering why things seem to be delayed?!
[_items replaceObjectAtIndex:0 withObject:item];
You're replacing every item in the array and wondering why only the last item is displayed?!
You shouldn't be sleeping the main thread at all. Remove that line; sending reloadData will make the table view redisplay itself as soon as possible.
To fill up the array, do this:
for (int i = 1; i <= 10; i++) {
NSString *item = [NSString stringWithFormat:#"%d", i];
[_items addObject:item];
}
[self.mainTableView reloadData];
Notice that I've moved the reloadData outside of the loop. You shouldn't send that until you've finished updating the data and are ready for the table view to redraw.

How can I make it repaint immediately after reloadData?
By returning after reloadData. There is no other way. You cannot cause the table view to redraw while your code is still running. Even if you used another thread, it wouldn't work the way you're envisioning.
If you want to modify the table over time, you can use an NSTimer to modify the data periodically, and then call reloadData (or better yet, reloadRowsAtIndexPaths:withRowAnimation:) after each change.
The user interface will never draw in the middle of your code. You have to return to let it update.
It is not possible for the UI to update while your code runs on the main thread. There are several approaches to providing UI updates during processing:
Do some work, schedule to do some more work with a timer or performSelector:withObject:afterDelay:, return so that the UI can update. This is what I described above.
Break your work into small units and schedule them using an NSOperationQueue or GCD queue. If you run these on the main queue, this is effectively the same as the first option; you're just letting the OS do the scheduling.
Run your processing on a background thread and update the data asynchronously. Periodically update the UI with a timer. This is good for simple progress meters, but it's hard to synchronize if you want the UI updates to be at specific points. This introduces all of the headaches of multi-threaded code, such as locking and atomicy.

Related

Any simple tutorial to load custom objects asynchronous on uitableview cells while scrolling

I am trying to find a simple tutorial for inserting custom objects asynchronous on uitableview cells while scrolling because my uitableview does not scroll nicely and smoothly. I have searched but i only have found about images asynchronous loading not helpful. I have an uiview that needs to be loaded asynchronous. Too much processing work is needed before the object loads as a result scrolling is not smooth.
Any help appreciated.
This is not as hard as it seems. There is only one caveat. You must know the height of your cell even when it is not fully loaded.
If the tableView has a constant row height, then set tableView.rowHeight. If you need to determine row height on the fly use UITableViewDelegate's –tableView:heightForRowAtIndexPath: callback.
Then in -tableView:cellForRowAtIndexPath dequeue a cell, set it to some initial state, kick off a NSOperation or a GCD block, and finally return the cell that you have reset.
In the NSOperation or CCG block you will perform the work you need, then call back into the main thread to set the values into the cell. This is the essence of async cell loading.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// dequeue a cell
// Reset the cell
cell.imageView.image = nil;
cell.textLabel.text = nil;
cell.detailTextLabel.text = nil;
// Use gcd
dispatch_queue_t queue = dispatch_queue_create("blah blah replace me blah", 0);
dispatch_async(queue, ^{
// Do work in the background
UIImage *image = value1;
NSString *text = value2;
NSString *detailText = value3;
dispatch_async(dispatch_get_main_queue(), ^{
// Back to main thread to set cell properties.
if ([tableView indexPathForCell:cell].row == indexPath.row) {
cell.imageView.image = image;
cell.textLabel.text = text;
cell.detailTextLabel.text = detailText;
}
});//end
});//end
dispatch_release(queue);
// Return the reset cell
return cell;
}

iPhone4 iOS5 is it safe to refresh UITableView from a background selector?

I have a UITableView on one of my view controllers, it is updated with log messages from multiple parts of my app, asynchronously. It worked fine, but today I noticed a weird bug. After about 2 hours, the entire tableview turned blank. there are no cells, no line separators, just the background color.
There are only 2 entry paths into this tableview.
NSMutableArray* tableViewCellData;
//in the init method:
tableViewCellData = [[NSMutableArray alloc]initWithCapacity:15];
-(void)setContextActionWithTitle:(NSString *)title description:(NSString *)description
ContextConsoleLogItem* temp = [[ContextConsoleLogItem alloc] initWithDate:[NSDate date] title:title message:description contextAction:kNoContextAction];
[tableViewCellData insertObject:temp atIndex:0];
[contextActionTableView reloadData];
}
//pretty standard data source management
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return kNumberOfSections;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [tableViewCellData count];
}
//here's how the cell displays itself.
#pragma mark -
#pragma mark Cell customization
-(void)configureCell:(UITableViewCell*) cell atIndexPath:(NSIndexPath*) indexPath
{
ContextConsoleLogItem* logItem = [tableViewCellData objectAtIndex:indexPath.row];
cell.backgroundColor = [UIColor blackColor];
cell.textLabel.textColor = [UIColor whiteColor];
cell.textLabel.font = [UIFont systemFontOfSize:9];
cell.detailTextLabel.textColor = [UIColor whiteColor];
cell.textLabel.text = logItem.message;
cell.textLabel.numberOfLines =3;
}
What could be causing the tableview to "lose" all of it's data and stop responding to
-(void)setContextActionWithTitle:(NSString *)title description:(NSString *)description
One thing that I suspect is that the NSMutableArray was allocated with insufficient capacity. It would reach maximum capacity pretty quickly. Some of the messages posted using the method above are coming from calls to performSelectorInBackground . Could it be that one of my background selectors hits the capacity of the NSMutableArray and fails to re-allocate it? But then again, even an empty tableview should still have cells, so I should be able to see line separators.
Should I wrap my calls to setContextActionWithTitle:description: with performSelectorOnMainThread ?
Could it be that 2 separate calls to update the tableview have been made and somehow they left the table in an inconsistent state? (I'm not getting any exceptions here)
This is a very puzzling behavior, and I'd appreciate any help in debugging it!
Never touch anything in UIKit from a background thread. Ever.
One thing that I suspect is that the NSMutableArray was allocated with insufficient capacity
Definitely not the case. As stated in the docs:
Mutable arrays expand as needed; numItems simply establishes the object’s initial capacity.
Jim is correct, never touch UIKit from the background thread.

Help with flickering when reloading an openGL scene

I'm starting to feel bad for asking so many questions and not being able to answer anyone's, but as soon as I find some that I can, I will! Confession out of the way...
Besically I have a gameLoop that runs everything in a game that I'm making and everything works fine in the menu and then in the game, but when I quit the game and then reload the menu screen the screen flickers.
Here is my game loop, I hope it provides some insight.
//A game loop that is triggered by a timer with intervals of 1/60 seconds
- (void)gameLoop
{
// we use our own autorelease pool so that we can control when garbage gets collected
NSAutoreleasePool * apool = [[NSAutoreleasePool alloc] init];
thisFrameStartTime = [levelStartDate timeIntervalSinceNow];
deltaTime = lastFrameStartTime - thisFrameStartTime;
lastFrameStartTime = thisFrameStartTime;
// add any queued scene objects
if ([objectsToAdd count] > 0)
{
[sceneObjects addObjectsFromArray:objectsToAdd];
[objectsToAdd removeAllObjects];
}
// update our model
[self updateModel];
// send our objects to the renderer
[self renderScene];
// remove any objects that need removal
if ([objectsToRemove count] > 0)
{
[sceneObjects removeObjectsInArray:objectsToRemove];
[objectsToRemove removeAllObjects];
}
[apool release];
if (needToLoadScene)
{
[sceneObjects removeAllObjects];
[self loadScene];
}
if (needToEndScene)
{
[sceneObjects removeAllObjects];
[self stopAnimation];
//We'll need to add unloading sounds later on
[inputController endScene];
[self renderScene];
needToEndScene = NO;
}
}
The only other thing that I should add is that the view is controlled by a class MusicAndViewController which I use as a view and then add subsequent views to. The menu and game views are actually the same openGL view but I render a clean view before switching to the other view
Anyone who has a similar problem to what I have had what solved it for me is this.
When setting a timer I had to override the setter so that it invalidated and then set the timer. On my gameScene I hadn't done this so when I make the timer = nil it continues to fire (as it hadn't been invalidated before setting it to nil, and was still running) and then cause a flickering on the next loaded scene (the menu)

NSTableView not updating like I think it should

I have the following code:
- (void)updateServerList {
[crazyStuff reloadData];
NSLog(#"Hi");
}
- (int)numberOfRowsInTableView:(NSTableView *)tableView
{
NSLog(#"Numbers have Changed");
return [serverBrowser.servers count];
}
- (id)tableView:(NSTableView *)tableView
objectValueForTableColumn:(NSTableColumn *)tableColumn
row:(int)row
{
NSLog(#"Starting updates for table");
NSNetService* server = [serverBrowser.servers objectAtIndex:row];
return [server name];
}
- (IBAction)tableViewSelected:(id)sender
{
//row = [sender selectedRow];
NSLog(#"the user just clicked on row ");
}
This is part of a chat program that I'm trying to expand. It was designed for the iOS and I'm working to get it to work on my laptop and chat with my iPad. I know that updateServerList is called correctly from my log statement. I also now that numberOfRowsInTableView: is called on startup but not anytime else. I would like to have my Table (the outlet crazyStuff) updated everytime updateServerList is called. How would I trouble shoot to see if it is or is not? I am not seeing the data show up in the table that "should" be there
I take it then that at startup, when numberOfRowsInTableView is called, it returns 0? If not, is tableView:objectValueForTableColumn:row: ever called then?
My first guess would be the "crazyStuff" is, in fact, not connected to anything or connected to something other than the table view you intend. Might want to log the value of crazyStuff in updateServerList.
Within updateServerList you should [crazyStuff noteNumberOfRowsChanged]. I thought reloadData causes the table to re-ask its dataSource for the numberOfRowsInTableView:, though...

NSTableView WILL NOT RELOAD

Hey guys, so in my newest program I use an NSTableView to display words on the left, and thier definitions on the right. these words and definitions are load from a .plist file, and at application startup the table view loads these words and definitions and displays them just fine. My problem comes in when the user tries to add a word and definition using the text boxes and buttons, the word is actually added to the .plist, meaning the method is running fine, but the table view refuses to display the new line. only until after i quit the program and reopen it does the tableview display the new line. I tested to see if the table view was connected properly by sending it other messages such as selectedRow and dataSource, all came back with responces, and proper responces at that. Currently the class that is used as the dataSource and delegate is a subclass to my main class with all my varibles and dictionaries. (I am big on using as little classes as possible). Lastly I tried inserting noteNumberOfRowsChanged in before reloadData, but still nothing.
I have tested everything and it just seems that the reloadData method is not initiating anything. Like I said, my table view is being sent the message, the new info is actually being added to the dicitinoary adn array, the amount of rows is being updated by the count method, and what proves it even more is that when the program is restarted it displays everything just fine. below is the relevent code, where currentWordList and currentDefitionList are the Array and Dictionary suppying the data to the dataSource, and editLibraryCardList is the NSTableView I am trying to reload.
the dataSource class code:
#interface EditorDataTable : SAT_Vocab_MacController {
IBOutlet NSTableColumn *editLibraryWordColumn;
IBOutlet NSTableColumn *editLibraryDefinitionColumn;
}
- (int)numberOfRowsInTableView:(NSTableView *)tableView;
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row;
#end
#implementation EditorDataTable
- (int)numberOfRowsInTableView:(NSTableView *)tableView {
return ([currentWordList count]);
}
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row {
if (tableColumn == editLibraryWordColumn) {
return [currentWordList objectAtIndex:row];
}
if (tableColumn == editLibraryDefinitionColumn) {
return [currentDefinitionList valueForKey:[[currentWordList objectAtIndex:row]lowercaseString]];
}
}
#end
method that adds the word to the list:
- (IBAction) editLibraryAddWordToLibrary: (id) sender {
if (self = [super init]) {
currentWordList = [[NSArray alloc] initWithContentsOfFile:userSATWordListPath];
currentDefinitionList = [[NSDictionary alloc] initWithContentsOfFile:userSATDefinitionListPath];
}
[currentWordList addObject:[[editLibraryNewCardWordInput stringValue]capitalizedString]];
[currentDefinitionList setObject:[editLibraryNewCardDefinitionInput stringValue] forKey:[[editLibraryNewCardWordInput stringValue]lowercaseString]];
aWordCounter = [currentWordList indexOfObject:[[editLibraryNewCardWordInput stringValue]capitalizedString]];
[aWordLabel setStringValue: [[NSString alloc] initWithFormat:#"%#", [currentWordList objectAtIndex: aWordCounter]]];
[aDefinitionLabel setStringValue: [[NSString alloc] initWithFormat:#""]];
[currentWordList writeToFile:userSATWordListPath atomically:YES];
[currentDefinitionList writeToFile:userSATDefinitionListPath atomically:YES];
[cardCountdownNumber setStringValue: [[NSString alloc] initWithFormat:#"%i", ([currentWordList count] - (1 + aWordCounter))]];
[editLibraryCardList noteNumberOfRowsChanged];
[editLibraryCardList reloadData];
}
Iv'e been stuck for days and any ideas will help! Thanks.
Zach
Have you tried debugging into your selectRowAtIndexPath method to make sure the reload occurs? (after you call [tableView reloadData] should be able to see this) Are you using UITableViewController?
If you wanted a callback after reload to know when its done, you could try:
[tableView reloadData];
[self performSelector:#selector(selectRowAtIndexPath:) withObject:indexPath afterDelay:0.0];
For those who are curious, i moved my code from the dataSource subclass to the main class, and it worked. i guess you cannot subclass the dataSource. Hope this helps!