MKNetworkKit - displaying cached images - objective-c

UPDATE AT BOTTOM
I branched off of the MKNetworkKit Flickr demo for this. I have several images on a webserver I want to display in a table. I have a UITableViewCell subclass, ImageCell.
Here is the custom method for retrieving remote images:
-(void)setImageForCell:(NSString *)remoteFileName {
self.loadingImageURLString =
[NSString stringWithFormat:#"http://myserver.com/%#.png",remoteFileName];
self.imageLoadingOperation = [ApplicationDelegate.imageDownloader imageAtURL:[NSURL URLWithString:self.loadingImageURLString]
onCompletion:^(UIImage *fetchedImage, NSURL *url, BOOL isInCache) {
if([self.loadingImageURLString isEqualToString:[url absoluteString]]) {
if (isInCache) {
self.imageView.image = fetchedImage;
[self.contentView drawRect:self.contentView.frame];
NSLog(#"Image is in cache");
} else {
self.imageView.image = fetchedImage;
[self.contentView drawRect:self.contentView.frame];
NSLog(#"Image is not in cache");
}
}
}];
}
//TableView.h
//it is called like this
//in cellForRowAtIndexPath...
ImageCell *cell = (ImageCell *)[tableView dequeue...etc];
MyObject *obj = [dataSource objectAtIndex:indexPath.row];
cell.textLabel.text = obj.name;
[cell setImageForCell:obj.name];
return cell;
I have inspected the contents of my default cache directory, and there are now items inside. Scrolling the table constantly now prints "Image is in cache". The problem is, the cell's imageView never updates. Mugunth has a class method automaticallyNotifiesObserversForKey: but I don't ever see it implemented anywhere. I'm guessing that there's another step involved to tell the tableView instance to update that row with the new contents.
Thanks for your input.
UPDATE
I got this to work by using a custom Interface Builder xib file with a Cell and a UIImageView IBOutlet. Not sure why it wasn't working with self.imageView.image, and would be interested to find out why, exactly. I still consider this an open question because I'd like to just use the standard UITableViewCell class.

I the call to prepareForReuse. Are you setting the self.imageView.image to nil?
If yes, dont do it because it looks like it's never re-initialized.

Why do you call "drawRect" from your code. That's blasphemy!
Inspect your imageView and check if the IB connections are good.

Related

Setting text and image on NSTableCellView

Hello I'm trying to use an NSTableView in my program and I'm having a problem setting the values for the NSTableCellView and getting them to display in the NSTableView. When I run my program, only blank cells show up. Using NSLog's, I can see that the cell imageView gets set, but doesn't display. When I go to set stringValues for the NSTableCellViews however, I only get null from my NSLog's despite the string containing data. Here's the delegate method I'm having a problem with:
-(NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
NSString *cellIdentifier;
NSImageView *pageImageView;
NSString *pageString;
int pageVotes;
if (_connectionArray.count == 0) {
return nil;
}
NSTableCellView *cellView = [[NSTableCellView alloc] init];
if (tableColumn == tableView.tableColumns[0]) {
cellIdentifier = #"firstColumn";
pageImageView = [[_connectionArray objectAtIndex:row] getImage]; //Gets imageView from Page Object
cellView.imageView = pageImageView; //Set image view for cell
NSLog(#"%#", cellView.imageView); //This works
}
if (tableColumn == tableView.tableColumns[1]) {
cellIdentifier = #"secondColumn";
pageString = [[_connectionArray objectAtIndex:row] getTitle];
cellView.textField.stringValue = pageString; //Set text for cell
NSLog(#"%#", cellView.textField.stringValue); //Does not work, returns null
}
if (tableColumn == tableView.tableColumns[2]) {
cellIdentifier = #"thirdColumn";
pageVotes = [[_connectionArray objectAtIndex:row] getVotes];
pageString = [NSString stringWithFormat:#"%i", pageVotes]; //Convert int to string
cellView.textField.stringValue = pageString; //Set text for cell.
NSLog(#"%#", cellView.textField.stringValue); //Does not work, returns null
}
[_tableView makeViewWithIdentifier:cellIdentifier owner:self];
return cellView;
}
I think everything set-up correctly between the Storyboard and the ViewController as well, but I could very well be wrong since this is my first time working with NSTableViews. I've also tried using:
[cellView setImage:pageImageView];
[cellView setTextField:[NSTextField textFieldWithString:pageString]];
but I run into the same issue. If anyone can help I greatly appreciate it! I feel like I'm missing something simple...
Setting the textField and imageView properties of NSTableCellView does not add a text field or an image view to the cell view. Those outlets are just intended to inform the cell view about which of its subviews are the primary text field and/or primary image view. You are still responsible for adding those views to the cell view as subviews or, possibly, as deeper descendant views.
Also, it's a bad idea for your model to vend views. That's not how it should work. Among other things, that will specifically interfere with adding those views to the cell view's subview hierarchy.
It's also strange that you're both creating the cell view and asking the table view to make it (by calling -makeViewWithIdentifier:owner:). Normally, you'd do one or the other, or first try -makeViewWithIdentifier:owner: and only create a view if that fails. And, of course, you wouldn't ignore the return value.
Frankly, the best thing to do is set this all up in Interface Builder. If you do it right, there's no need to implement -tableView:viewForTableColumn:row: at all. Is there a reason you didn't go that route?

UICollectionView not reloading cell's imageView

I have the below code called within :
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
Unfortunately, when I call [collectionView reloadItemsAtIndexPaths:#[indexPath]]; the imageView within the does not update. If I call reloadData, it does. I'm trying to keep the code as efficient as possible, so I do not want to just call reloadData. Would anyone have some insight on why this isn't working or what else I need to do? I've already had a few other issues with loading up numerous API image queries that conflict with the collectionView's lovely endAnimations stuff. Any help would be appreciated.
SearchResultsCollectionViewCell *searchResultCell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass([SearchResultsCollectionViewCell class]) forIndexPath:indexPath];
Product *product = self.products[indexPath.row];
UIImage *productImage = [self.imageCache imageForURL:product.fullImageUrl];
if (productImage) {
searchResultCell.productImageView.image = productImage;
} else {
[self.imageCache downloadImageForURL:product.fullImageUrl indexPath:indexPath withCompletion:^(UIImage *image, NSError *error) {
if (!error) {
searchResultCell.productImageView.image = image;
// [collectionView reloadData];
[collectionView reloadItemsAtIndexPaths:#[indexPath]];
}
}];
UIImage* image = [UIImage imageNamed:#"001_logo"];
searchResultCell.productImageView.image = image;
}
The way you are accessing and storing the images seems odd.
The way I would do this is to create a #property for a UIImage on the productImage.
Then you can ask the product for its image. If the product returns a nil image then put a placeholder in the cell.
When the download is complete you can then reload the cells.
Secondly, I wouldn't try to change the cell in the completion block of the download call you are making.
The completion block should update the model (I.e. Store the image into the product object) and then reload the cell.
The reason for this is that you don't know for certain that the cell you are running the completion block on is (a) the same cell as the one you started it on or (b) it is still displaying the same product.

What are the possible reasons why -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath didn't get called?

I got a really strange problem.
My tableView has all the delegate and datasource set up.
Everything is fine.
However, clicking the rows do not activate:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
I used custom cells.
After I click and click and click and click and click, sometimes it goes through.
I wonder what can possibly cause that? It's as if the customCell is "absorbing" the touch event or something or what?
Could that be why? If so, if we want to implement customCell and we want the tableView to handle the touch up event, what should we do?
Additional symptom:
If I remove user interaction enabled from the custom cell then the problem is solved with a catch.
However, clicking the button will somehow erase all the label texts in the customCell.
The implementation of the custom Cell is the following:
- (BGUIBusinessCellForDisplay *) initWithBiz: (Business *) biz
{
if (self.biz == nil) //First time set up
{
self = [super init]; //If use dequeueReusableCellWithIdentifier then I shouldn't change the address self points to right
NSString * className = NSStringFromClass([self class]);
//PO (className);
[[NSBundle mainBundle] loadNibNamed:className owner:self options:nil];
self.frame =self.view.frame;
[self addSubview:self.view]; //What is this for? self.view is of type BGCRBusinessForDisplay2. That view should be self, not one of it's subview Things don't work without it though
}
if (biz==nil)
{
return self;
}
_biz = biz;
self.prominentLabel.text = [NSString stringWithFormat:#"Isi: %#", biz.isiString];
self.Title.text = biz.Title; //Let's set this one thing first
self.Address.text=biz.ShortenedAddress;
//if([self.distance isNotEmpty]){
self.DistanceLabel.text=[NSString stringWithFormat:#"%dm",[biz.Distance intValue]];
self.PinNumber.text =biz.StringPinLineAndNumber;
NSString * URLString=nil;
if(biz.Images.allObjects.count!=0){
//self.boolImage=[NSNumber numberWithBool:true];
Image * image=(biz.Images.allObjects)[0];
URLString = image.URL;
URLString = [NSString stringWithFormat:#"http://54.251.34.144/thumbnailer/Thumbnailer.ashx?imageURL=%#",URLString.UTF8Encode];
//url=[NSURL URLWithString:image.URL];
}else{
float latitude = biz.getCllLocation.coordinate.latitude;
float longitude = biz.getCllLocation.coordinate.longitude;
URLString = [NSString stringWithFormat:#"http://maps.googleapis.com/maps/api/staticmap?&zoom=16&size=160x160&maptype=roadmap&sensor=true&center=%f,%f&markers=size:small|color:blue|%f,%f",latitude,longitude,latitude,longitude];
URLString = URLString.UTF8Encode;
}
//Should add code and add loading indicator here
[BGHPTools doBackground:^{
UIImage * imageBiz = [BGMDImageCacherAndDownloader getImageFromURL:URLString];
[BGHPTools doForeGround:^{
self.Image.image=imageBiz;
[self.Image makeRound];
}];
}];
//self.view=self.view;
/*if (self.tableViewCell == Nil)//Instantiate that tableviewCell
{
PO(self.tableViewCell);
}
self.tableViewCell.business = bis;
self.pinLbl.text = bis.StringPinLineAndNumber;
self.lblTitle.text=bis.Title;
//self.pinLbl.text=bis.pinNumber;*/
//}
/*self.name=[dict objectForKey:#"Title"];
self.address=[dict objectForKey:#"Street"];
CLLocation * cll=[[CLLocation alloc]initWithLatitude:[[dict objectForKey:#"Latitude"] doubleValue] longitude:[[dict objectForKey:#"Longitude"] doubleValue]];
self.distance=[NSNumber numberWithDouble:[cll distanceFromLocation:[cachedProperties currentLocation]]];*/
return self;
Update: I already figure out why the texts are gone. Turns out my background is white. When a row got selected, the text suddenly turn into white. So by setting selected style to blue I sort of get that "fixed".
However, I still do not see where in my code I specify that all label texts should be white if the underlying tableViewCell is selected.
After all, what's selected is the cell, not the label. How the hell the label knows that it has to turn white is beyond me.
If you are using a Storyboard to handle the interface, instead of using:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
Try using
#pragma mark --- Push selectedObject to the detailView ---
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
RRAppDelegate *myAppDelegate = [[UIApplication sharedApplication]delegate];
if ([[segue identifier] isEqualToString:#"PushObjectSegue"]) {
NSIndexPath *selectedRowIndex = [self.tableView indexPathForSelectedRow];
RRObjectViewController *detailViewController = [segue destinationViewController];
detailViewController.selectedObject = [myAppDelegate.goals objectAtIndex:selectedRowIndex.row];
}
}
I was having the same problem with the method you used and instead used this, it started working perfectly. Of course you'd have to adapt the code to your app's viewControllers and data source because I used my AppDelegate as the datasource, and I wasn't using a custom cell.
The most likely thing is that a view in your custom cell is absorbing the touch. Sometimes this is what you want, e.g. a button that does something, rather than selecting the entire cell. Assuming you don't want this, then just set those views' userInteractionEnabled property to NO.
--Additional code for custom NIB loading.
All you have to do is register the NIB in your viewDidLoad routine:
[tableView registerNib: [UINib nibWithNibName:#"yourCellNibName" bundle:nil] forCellReuseIdentifier:#"yourCellTypeID"]
and then in your cellForRowAtIndexPath just call:
newCell = [tableView dequeueReusableCellWithIdentifier #"yourCellTypeID"];
...
return newCell;
And it will load a cell from your XIB (or give you one from the previously used queue).
I just want to update that I think I have figured out what the problem is but still can't solve that quite right yet. And well the update is comprehensive so I think it should be an answer though I hope it's not the answer because some puzzle is still missing.
All the problem is interrelated.
The problem is in this line:
[self addSubview:self.view];
I basically turn that into:
Basically the my custom view cell has a view whose type is also tableViewCell. That view cover the real tableViewCell.
That's why when user interaction is enabled, that view will absorb the user's interaction.
That's also why the label "disappear". What happen is the label doesn't disappear. The label got highlighted and become white. However, what's highlighted is the tableViewCell not the opague view. The white opague self.view is still white while the tableCell itself is tinted with blue. So the label becomes white in the middle of white background and is gone.
I think I should replace [self addSubview:self.view] into self= self.view
However, that would mean changing the value of self. Yes it's in init. But it's still awkward. If anyone has the WAY to implement custom subclass of UI with XIB it'll be great because I haven't found one till now.
Awkward.
I wonder if we can draw a pointer to an XIB and specify that the outlet is self itself.
If that fail, I'll set background of self to white and background of self.view to transparent.
After tons of error and trying I did this:
self.contentView.backgroundColor = [UIColor whiteColor];
//self.view.backgroundColor = [UIColor redColor];
self.frame =self.view.frame;
/*PO(self.view.subviews);
PO(self.subviews);
PO(self.Title.superview);
PO(self.Title);
PO(self.view);
PO(self.Title.superview);
PO(self.view.contentView);*/
//Suck all the subviews from my minions
for (UIView* aSubView in self.view.contentView.subviews) {
[self.contentView addSubview: aSubView];
//[self.contentView add]
}
Basically I "move" all the subViews of my view object to my self object. There is a catch though that when subclassing tableViewCell I should move the subviews of the contentView. Who knows why.
At the end I just set self.view to nil for it's no longer needed and my program works as expected.
Also to set background of your tableViewCell, you need also to set the background of self.contentView rather than self.view.
Another approach you can try is to use story board. Alternatively you can just move the contentView of the self.view to self.
Make sure you'r implementing that method and not
deselectRowAtIndexPath:animated

Activity indicator only shows in one cell

I have a UITableView filled with cells that display file names, and the cells also indicate the upload/download progress of the files. When a file is being moved, its corresponding cell should show a UIActivityIndicatorView in its accessory view. I have a UIActivityIndicatorView set up and ready to go in viewDidLoad, but when I tried to set it as the accessory view for multiple cells, it only shows up in one cell.
-(void)viewDidLoad {
[super viewDidLoad];
activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[activityIndicator startAnimating];
}
//...code that detects file changes and calls fileChange
-(void)fileChange {
for (int i = 0; i < [self.cloudNames count]; i++) {
//detect whether file name in array is uploading, downloading, or doing nothing
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
if (downloading) {
cell.accessoryView = activityIndicator;
//other changes here specific to downloads
} else if (uploading) {
cell.accessoryView = activityIndicator;
//other changes here specific to uploads
} else {
cell.accessoryView = nil;
}
}
}
As I said, the activity indicator only shows in one cell even where there are multiple cells that should show it.
I don't want to set up the UIActivityIndicatorView in the fileChange method (even though it works) because this method is called many times during the upload/download. If the method is called and the activity indicator is set up there, the activity indicator resets in all of the table view cells when the method is called, resulting in glitchy and unsmooth animations, and it causes a huge memory problem.
Any ideas of what to do? Thanks.
Even if you are setting the activity indicator for the cell, you only ONE instance variable for it. The way to do this is to create an indicator for each cell inside tableView:cellForRowAtIndexPath:
You can set a tag for the UIActivityIndicatorView and whenever you want to access it or grab it you can get the cell, and get the indicator view using [cellView viewWithTag:theTag]. No need for instance variables.
If you want to make things even fancier you can subclass UITableViewCell and do whatever you want to do inside your custom cell..
EDIT:
To get the view you can either assign to the accessory view and just get the cells accessoryView:
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
UIActivityIndicatorView *indicator = (UIActivityIndicatorView *) cell.accessoryView;
or you can add the UIActivityIndicatorView to the cell`s contentView (that way you can place it where ever you want, you have more flexibility):
adding the indicator:
myIndicatorView.tag = 1;
[cell.contentView addSubview:myIndicatorView];
getting the indicator:
UIActivityIndicatorView *indicator = [cell.contentView viewWithTag:1];
hope this helps

Crashing App when scrolling UItableView and hitting back

I am using a navigation controller to get to the UITableView. In this UItableView, there is a search bar and 50 cells. When i don't scroll and then hit back, the application acts normally but when i scroll down like 10 cells and then hit back, my application crashes with EXC_BAD_ACCESS Error. Any idea wat may be the reason of this crash?
In dealloc, I am releasing all the objects I created in the header file:
- (void)dealloc
{
[listContent release];
[filteredListContent release];
[tmpCell release];
[cellNib release];
[super dealloc];
}
and for the function creating the cells, it is as follows: ( Note I am doing an alternate UItableView with a searchBar)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *kCellID = #"cellID";
ApplicationCell *cell = (ApplicationCell *)[tableView dequeueReusableCellWithIdentifier:kCellID];
if (cell == nil)
{
[self.cellNib instantiateWithOwner:self options:nil];
cell = tmpCell;
self.tmpCell = nil;
}
/*
If the requesting table view is the search display controller's table view, configure the cell using the filtered content, otherwise use the main list.
*/
NSDictionary *dataItem;
if (tableView == self.searchDisplayController.searchResultsTableView)
{
dataItem = [self.filteredListContent objectAtIndex:indexPath.row];
}
else
{
dataItem = [self.listContent objectAtIndex:indexPath.row];
}
// Display dark and light background in alternate rows -- see tableView:willDisplayCell:forRowAtIndexPath:.
cell.useDarkBackground = (indexPath.row % 2 == 0);
// Configure the data for the cell.
cell.icon = [UIImage imageNamed:#"iTaxi.jpeg"];
cell.publisher = [dataItem objectForKey:#"Number"];
cell.name = [dataItem objectForKey:#"Name"];
cell.price = [UIImage imageNamed:#"order-taxi.png"];
return cell;
}
ViewDidUnload has the same code as dealloc
That error occurs because somewhere in your code you're setting scrollEnabled to "NO" (probably when you activate the searchbar):
self.tableView.scrollEnabled = NO;
I mean, if your searchText length is equals to 0 (you just entered on the search mode), you cannot disable the tableview scroll.
Hope this helped you.
Good luck, good coding!
Fábio Demarchi
When you press back while the tableview is being scrolled, the app will get crashed since the deallocated tableview instance's datasource(rarely delegate) protocol's method being called. So we could get the crash since we're accessing deallocated instance.
To avoid this just add the dealloc method in the particular view controller class, and set the corresponding protocol's to nil.
-(void)dealloc {
self.yourTableView.delegate = nil;
self.yourTableView.dataSource = nil;
}
Happy Coding :)
This is because the cells are recreated for visible rows. That is, cellForRowAtIndexPath is called for visible rows when you scroll the tableView. Remove that condition if(cell==nil) in cellForRowAtIndexPath.