Loading a UITableViewCell with a NSOperationQueue - objective-c

Hi guys I have an UITableView that loads precomputed cells from NSMutableArray. I want to use NSOperationQueue or PerformSelectorOnMainThread to update the user interface to enable smooth scrolling but i get an error... this is my code...
- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//queue is being initialized in viewDidLoad
[queue addOperationWithBlock:^ {
[[NSOperationQueue mainQueue] addOperationWithBlock:^ {
NSLog(#"Updating...");
return [self.CellObjects objectAtIndex:indexPath.row];
//if you remove the line above with the return, NSOperationQueue will work but I need the above line to load the cell.
}];
}];
}
Is there a way to make it work? Any help appreciated!

Why not simple ...
return [self.CellObjects objectAtIndex:indexPath.row];
...?
It's a mess. Why do you have two calls to addOperationWithBlock:? And also your return statement has nothing to do with return value of tableView:cellForRowAtIndexPath:. It's return value of your block, so, it will never work.
What's your error? I assume it's about incompatible block pointer, because it expects void(^)(void) and you're trying to send UITableViewCell *(^)(void).
Blocks are not gonna help you there. If you have precomputed cells in CellObjects, just use only the return self.CellObjects[indexPath.row]; line.
Also don't use property names like CellObjects. Should be named cellObjects. Check the case.

Offsetting your cell retrieval in that way is not going to give you any advantage. You need to compute your cell height/size/content prior to your UITableView instance asking for cells.
UITableView is expecting a UITableViewCell to be returned from that delegate callback on the
main thread.
A better idea would be to place the computation on other threads if they require time and their on completion you can call back to your UITableView to reloadData.

Related

collectionView:cellForItemAtIndexPath: doesn't get called

I want to add new cells in my collection view, but nothing shows up when I add data.
I have a custom UICollectionViewLayout class, which has been working just fine, and I've been keeping dummy data in my datasource to adjust the layout. Now that I got rid of the dummy data, nothing's showing up.
Since the app didn't break and there weren't any warnings, it was difficult to track down where the problem was, and here's where I found a clue:
(UICollectionViewLayout class)
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSLog(#"ElementsInRect: – Visible cells info: %#", [self.collectionView.visibleCells description]);
...
}
Here, -visibleCells returns an empty array, even when I add data, call -reloadData and invalidate the layout. So I placed a breakpoint in -collectionView:cellForItemAtIndexPath:, and it turns out this method is not called at all. How did the cells show up before?
Any help would be appreciated.
The data source method, collectionView:numberOfItemsInSection:, has to return a non-zero number for collectionView:cellForItemAtIndexPath: to be called. When you had dummy data in your data source, it was. Now that you removed that dummy data, that method is probably returning 0. When you add data, it should put items into your data source, and then a call to reloadData should work. You should put a log in collectionView:numberOfItemsInSection:, and see what it's returning.
Okay, it turns out the issue was in UICollectionViewLayout. I doubt anyone else will be having this problem, but I'll write my answer for the sake of completeness:
I'd been tweaking my custom UICollectionViewLayout class, and after I'd thought that it was working well, I made the code look neat by deleting old code that was commented out, move methods, etc.
While doing that, I recalled having read somewhere that it's good practice to create attributes in -prepareLayout method, and return those attributes when -layoutAttributesForItemAtIndexPath: or -layoutAttributesForElementsInRect: is called. For me, it was a matter of moving a block of code, so I thought no biggie. And during this "cleaning process" I must have made a mistake.
What's really frustrating is that the code itself actually works regardless of where the attributes are created, and I can't tell what went wrong for the last few days.
The following is a snippet of code that I used to create the attributes objects. My initial question was asking why -collectionView:cellForItemAtIndexPath: was not called while executing the 3rd line. I did not change this part of the code, other than moving it around.
for (int i = 0; i < 3; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:self.topLayer];
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath];
if (cell) {
UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath];
[self.array addObject:attributes];
} else {
NSLog(#"prepLayout: the cell doesn't exist for the index path {%d – %d}", indexPath.section, indexPath.item);
}
}
Number of Rows in Section - the count that can be used will determine if the cellForItemAtIndexPath gets called.
Initially when the view loads this will be called. Within the numberOfItemsInSection, if you have an array, the [array count] might return a nil value.
Complete the procedure where the array is populated, then reload the data in the collection view which will re-assess the numberOfItemsInSection. This can be done with the following code:
[self.myCollectionView reloadData];
"myCollectionView is the name given to the collection view item in your view"

UITableViewDataSource and Multithreading

I'm running into index beyond bounds exception in one of my UITableViews and I think it could be down to some multithreading issues. Here's what I believe is happening:
I have a UITableView and it's data source is a regular NSMutableArray.
This NSMutableArray which is backing my UITableView is updated every couple of seconds with the contents of an API response.
After each update, UITableView's reloadData is being invoked to ensure that the user sees new data from the API server.
Sometimes a index beyonds bounds exception gets thrown.
Here's my code:
-(NSMutableArray*) currentBetEvents
{
return currentMarketId == nil ? [[BFOpenBetsModel sharedInstance] betEvents] : filteredBetEvents;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSArray *betEvents = [self currentBetEvents];
return [betEvents count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSArray *betEvents = [self currentBetEvents];
id obj = [betEvents objectAtIndex:indexPath.section] // this is where it blows up
Basically, I get an exception while trying to access an object in the betEvents structure at index 0.
What I believe is happening is:
reloadData is called on the UITableView
numberOfSectionsInTableView: is invoked which returns a value > 0.
a rouge thread arrives and clears out the UITableView's data source.
cellForRowAtIndexPath: is invoked and it bombs.
Is there any way to ensure that this doesn't happen? Do I need to start using some primitive locks on the data source to ensure that it doesn't get updated while the table is being updated?
EDIT
Took another look at how the data structures returned by currentBetEvents can be altered and it looks like the filteredBets & betEvents can be cleared out as a result of the following code:
[[NSNotificationCenter defaultCenter] postNotificationName:kUserLoggedOutNotification object:nil];
This notification is posted whenever the user logs out. Whenever a user logs out of the app, I need to clear out the filteredBets and betEvents arrays. Is it possible that the following could happen:
reloadData is called on the UITableView
numberOfSectionsInTableView: is invoked which returns a value > 0.
User logs out which kicks off the notification & clears out the data structures.
cellForRowAtIndexPath: is invoked and it bombs.
Thanks,
Sean
Definitely sounds like a threading problem. You might try something like this:
// view controller
#synchronized([[BFOpenBetsModel sharedInstance] betEvents])
{
[self.tableView reloadData];
}
…
// data model
#synchronized(_betEvents) // or whatever the instance variable -betEvents returns is
{
[_betEvents addObject:whatever];
}

How to pass data (arrays) between functions

I must use s7graphview library for draw simple histogram, and I've got custom function called
-(IBAction)histogram:(id)sender;. in this function every pixel from image is passed to array as RGB representation. then pixels are counted and I've got red, green and blue array. I can send to NSLog or something but problem is, when I try to send 3 arrays to - (NSArray *)graphView:(S7GraphView *)graphView yValuesForPlot:(NSUInteger)plotIndex;. both functions are in the same .m file, and I have no idea how to pass data between them, because when I write redArray, Xcode don't suggest me this name.
Since - (NSArray *)graphView:(S7GraphView *)graphView yValuesForPlot:(NSUInteger)plotIndex is a delegate method, it should be implemented in your class that is posing as a delegate to S7GraphView object. You don't call explicitly, you define it as such in your .m implementation:
- (NSArray *)graphView:(S7GraphView *)graphView yValuesForPlot:(NSUInteger)plotIndex
{
if ( plotIndex == <some index value> )
return redArray;
else
return nil;
}
I have no idea what plotIndex corresponds with your various color arrays, but you should get the idea.
When the S7GraphView object needs that data, it will invoke that delegate method.
This is not unlike implementing UITableViewDelegate and UITableViewDataSource methods. When a UITableView method -reloadData is invoked, it will call upon your view controller (presuming it is delegate/data source of the table) to supply UITableViewCell objects via
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = <... dequeue or created ... >.
/*
do some cell set up code based on indexPath.section and indexPath.row
*/
return cell;
}
Similar with S7GraphView I'm sure (I don't have the API to see all it does). In your IBAction method, you will probably be doing something like:
- (IBAction)histogram:(id)sender
{
// maybe you recalculate your red, green, and blue component arrays here and cache
// or maybe you calculate them when requested by the delegate method
// tell the S7GraphView it needs to update
// (not sure what the reload method is actually called)
[self.myS7GraphView reloadGraph];
}

Objective C - UITableViewCell crashing on scroll

I have a UITableViewCell with a method like this.
-(void) setupStore:(StoreModel *) store {
self.title.text = store.title; // crash here when scrolling
}
So that method is called from within a UIViewController class that contains the UITableView.
Something like this
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
...
[cell setupStore:[storesArray objectAtIndex:indexPath.row]];
...
}
That works when the table first loaded, but when I scroll the table, it crash with error EXC_BAD_ACCESS.
What could be causing that?
Please enlight.
Thanks,
Tee
Try to build your code with NSZombieEnabled = YES and report here what is happening. Give us the full error description.
http://cocoa-nut.de/?p=16
In general We will get EXC_BAD_ACCESS when we are trying to use a released object.
So you can check whether you are using any released object.
As you have mentioned that storesArray = [[[storesLocation alloc] init]retain]; there is no need to retain the object. Give a try by using this line
storesArray = [[storesLocation alloc] init];.
Also make sure that storemodel object exists by logging it in this method
-(void) setupStore:(StoreModel *) store
{
NSLog(#"store model %#",store);
}
You can go through the link
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmPractical.html

How to access cell.textLabel.text in tableView:heightForRowAtIndexPath: from tableView:cellForRowAtIndexPath:

In the tableView:heightForRowAtIndexPath: method I need to get the length of cell.textLabel.text in tableView:cellForRowAtIndexPath. How can this be done?
Calling cellForRowAtIndexPath involves calling heightForRowAtIndexPath, so if you call it from heightForRowAtIndexPath, you end up in an infinite recursion.
What you can do is use your data source to examine the same data you would use to populate the cell in cellForRowAtIndexPath, and then use get the length of the text you're going to insert into cell.textLabel.text (or use NSString UIKit additions to compute what the size of your label will be).