Each UITableViewCell contains a UICollectionView. Having trouble figuring out why some UICollectionViewCell images are not showing up. I'm using SDWebImage library to load images. The method - (void)sd_setImageWithURL:(NSURL *)urlcalls [self sd_cancelCurrentImageLoad];. I think cancelling the previous request is the problem, but read somewhere that will create a race condition. Problem doesnt happen for every UITableViewCell, and if I tap on cell for details and go back, the images fill in. Perhaps I need to reload data somewhere. Any tips on how to solve this?
if you try to image fatch from server to try this.
#import "UIImageView+WebCache.h"
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
NewProfileCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
[cell.img_user_image sd_setImageWithURL:[NSURL URLWithString:[mutDict valueForKey:#"UserImageURL"][indexPath.row]] placeholderImage:[UIImage imageNamed:#"No_IMAGE_PLACE"]];
return cell;
}
Try it:
- (void)prepareForReuse
{
[super prepareForReuse];
[self.imageView cancelCurrentImageLoad];
[self.imageView setImage:[UIImage imageNamed:#""] forState:UIControlStateNormal]; // Placeholder image
}
Related
I'm not using storyboard, everything is done by code..
and when I scroll the UICollectionView.. after it reusing correctly..some cells..
than it happen :
-the cell initWithFrame is being call
-new gray hair appear on my head.
I read other q/a and check maybe it's something with threads but all the reloadData is on the main thread.
any directions ?
I have no idea what's your code, so I'll propose how do I do it:
// somewhere in eg viewDidLoad
[self.collectionView setDelegate:self];
[self.collectionView setDataSource:self];
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:CellId];
And later the delegate:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = cell = [collectionView dequeueReusableCellWithReuseIdentifier:MyCellId forIndexPath:indexPath];
// do something with your cell, set title or anything
return cell;
}
There is another possibility. Your cell, as it's reusable, will have already saved previous properties. So if you did something like this:
if (iCanAddGrayHair) {
[cell.imageView setImage:[UIImage imageNamed:#"hair"]]
}
Then do notice, that new cell will still have this image set! You need to add:
else {
[cell.imageView setImage:nil];
}
To reset it from previous state. You can also override prepareForReuse: method of UICollectionViewCell class to reset the values (don't forget to call super).
I have a uitableview where I use a custom cell. However, when I scroll the table view there is some serious lag. It happens when I set the UIImaveView's image property with an image. I am accessing an image from the directory. But since file IO is slow I am using the dispatch_async to load the image into a UIImage object on a separate thread.
However there is still some lag. When I scroll up and down the rows without any images, the scrolling is very smooth. However when the row actually has an image, there is lag. the momentum scrolling will halt, then the app becomes unresponsive, then when the image finally loads the momentum continues where it left off.
I am not sure what is causing the lag. At first I thought it had to do with the image being too large so i tried scaling it down. Still lags. Again, if I don't set the image in the custom cell there is no lag. But when I do set it there is lag. I am not sure how to fix this.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
DHTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kReuseIdentifierGoalCell forIndexPath:indexPath];
[self configureCell:cell forIndexPath:indexPath isForOffscreenUse:NO];
return cell;
}
- (void)configureCell:(DHTableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath isForOffscreenUse:(BOOL)offscreenUse {
if (cell == nil) {
return;
}
[cell setDelegate:self];
PATH_TO_FILE = SQLITE_QUERY_TO_GET_PATH; //some pseudo codes
__weak typeof(sSelf)wSelf = sSelf;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__strong typeof(wSelf)sSelf = wSelf;
UIImage *unscaled_image = [UIImage imageWithContentsOfFile:PATH_TO_FILE];
UIImage *image = [unscaled_image imageScaledToFitInSize:kCellUIImageSize];
__weak typeof(sSelf)wSelf = sSelf;
dispatch_async(dispatch_get_main_queue(), ^{
__strong typeof(wSelf)sSelf = wSelf;
DHTableViewCell *cell = (id)[sSelf.tableView cellForRowAtIndexPath:indexPath];
if (cell) {
[cell.imageStored setImage:image]; //Commenting this out relieves all lag
}
});
});
}
#Calimari328 I hate if to put this as an answer because it does not really answer your question but the truth is what you should really do is use a library to achieve this. for example SDWebImage
Example:
#import <SDWebImage/UIImageView+WebCache.h>
...
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:MyIdentifier] autorelease];
}
// Here we use the new provided setImageWithURL: method to load the web image
[cell.imageView setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
cell.textLabel.text = #"My Text";
return cell;
}
You have to rethink this, cells get reused, that means each time you scroll your app will try to download images again.What about performance? memory ? cache ? processing?
As you think more about it is a lot more complex then a simple async task. Fortunately there are opensource projects to achieve this. No need to reinvent the wheel.
Please note I am not advertising any library if you want to write your own code you can do this as well. You can also search on the web for easy ones to implement.
In my app I have this code:
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return images.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
static NSString *identifier = #"gallerycell";
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
UIImageView *backImageCell = (UIImageView*)[cell viewWithTag:100];
[backImageCell setImage:[images objectAtIndex:indexPath.item]];
if([indexPath row] == ((NSIndexPath*)[[collectionView indexPathsForVisibleItems] lastObject]).row){
[activity_view stopAnimating];
[UIView animateWithDuration:0.8 animations:^{
back.alpha = 0;
}];
}
return cell;
}
the array images contains UIImage of 200x150 size, and their dimension in kb is about 42kb, a normal array of UIImage.
When I reload data for this collectionview I have a memory warning after 15 image... is there a way (as a thread) to don't have a memory warning?
Don't store Images to Array, that's not a good practice. As the number of images or size of images increase it'll throw memory warnings and crash.
Alternatives:
Store image names in array
Store file path in array
Store image url in array and use Async methods to load the image to your UITableView or UICollectionView
In addition to resizing the images, you can nil out the image when the cell scrolls out of view - just implement:
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
Also, a trick to maintain a cache that will not swamp the system is to do as Midhun suggested in the comments - use imageWithContentsOfFile. Then create a NSCache, and stuff the images in it using the image name or some other identifying key. When you need an image, if its in the cache, pull it out. If not you can read it from the file system again. iOS will purge a NSCache if it needs more memory.
I have a UICollectionView controller embedded inside a navigation controller. The collectionView lists projects and each cell is supposed to segue to a ProjectDetail screen.
I simply cannot get the segue to trigger. If I simply drop a button on the nav bar and hook up a segue to the detail, it works. But triggering from my CollectionView cell doesn't.
Here is what the storyboard looks like: http://cl.ly/RfcM I do have a segue hooked up from the CollectionViewCell to the ProjectDetailViewController
Here's the relevant code inside my ProjectDetailViewController:
#interface ProjectCollectionViewController () {
NSArray *feedPhotos;
Projects *projects;
}
#end
#implementation ProjectCollectionViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.collectionView registerClass:[FeedViewCell class] forCellWithReuseIdentifier:#"cell"];
[self loadData];
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"selected %d", indexPath.row);
Project *project = [projects getProject:indexPath.row];
NSLog(#"project = %#", project);
}
- (void)loadData {
[self.projectLoader loadFeed:self.username
onSuccess:^(Projects *loadedProjects) {
NSLog(#"view did load on success : projects %#", loadedProjects);
projects = loadedProjects;
[self.collectionView reloadData];
}
onFailure:^(NSError *error) {
[self handleConnectionError:error];
}];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return projects.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = #"cell";
FeedViewCell *cell = (FeedViewCell *) [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
cell.backgroundColor = [UIColor colorWithRed:0.0 green:0.0 blue:1.0 alpha:1.0];
UIImageView *cellImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
Project *project = [projects getProject:indexPath.row];
NSString *imageUrl = [project coverPhotoUrl:200 forHeight:200];
NSLog(#"imageurl =>%#", imageUrl);
if (imageUrl) {
[cellImageView setImageWithURL:[NSURL URLWithString:imageUrl]];
}
[cell addSubview:cellImageView];
cell.imageView = cellImageView;
return cell;
}
I'm guessing the problem is somewhere in how I'm hooking up the Cells to the CollectionView.
Any help would be greatly appreciated!
You cannot create segues directly from cells in a storyboard because the collectionview is populated dynamically through the data source. You should use the collectionView:didSelectItemAtIndexPath: and perform the segue programatically using performSegueWithIdentifier:sender:. Something like this:
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
[self performSegueWithIdentifier:#"MySegueIdentifier" sender:self];
}
where MySegueIdentifier is the identifier of the segue defined in storyboard.
TLDR: FOR A STORYBOARD, do not call registerClass:forCellWithReuseIdentifier:. It overrides what the storyboard sets up for the cell (including how segues are handled):
How to set a UILabel in UICollectionViewCell
Brief setup
Used a storyboard
Created a new collection view controller using the Xcode template,
setting it as a subclass of UICollectionViewController.
Initially used the default UICollectionViewCell, adding a UILabel
programmatically.
The generated UICollectionViewController code registered the cell in viewDidLoad:
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:reuseIdentifier];
First Issue:
The prepareForSegue:sender: event was not firing, which brought me to this answer .
I implemented the UICollectionViewDelegate and collectionView:didSelectItemAtIndexPath: event, then called the segue programmatically.
This fixed my first issue.
Second Issue: I switched to a custom cell containing one label. After hooking everything up, the cell label was not displaying.
After some digging, I found a solution contained in the link at the top of my answer.
Third Issue and Solution: I removed the registerClass:forCellWithReuseIdentifier: line. When I ran my app, the label appeared correctly, but when I tapped a cell, it called the prepareForSegue:sender event twice. By removing the registerClass:forCellWithReuseIdentifier line, the cell was processing cell touches directly, without the need of the delegate method. This is how I expected the storyboard to work. I deleted the collectionView:didSelectItemAtIndexPath: event, which resolved the double-firing of prepareForSegue:sender:. If you are using a storyboard, do not register the cell class. It overwrites what storyboard sets up.
Have you made your CollectionView Cell's connection in Triggered Segues on selection?
You can also trigger a segue programatically using
[self performSegueWithIdentifier:#"segueIdentifier" sender:nil]
in
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
Equivalent Swift code for similar question.
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier(#"TargetSegway", sender: self)
}
Make sure, in case if your cell has other overlapping views, "User Interaction Enabled" is unchecked (you can find this option, under attribute inspector View/Interaction). Otherwise, your Tap Gesture is consumed by the overlapping view, didSelectItemAtIndexPath may not be called.
I am just a beginner. i want to display image which is stored in database in a collection view cell. I have already created database and collection view. my code is as follow,
MyDatabase *data;
data=[MyDatabase new];
imagearray=[data OpenMyDatabase:#"SELECT pic_name FROM exterior" :#"pic_name"];
so my question is how can i display images ? let me know the way/code
thanks in advance
You can use following code for display UIImage grid. Add UICollectionView for your xib file. Don't forget to set the delegate in collection.
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return noOfItem/ noOfSection;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section
{
return noOfSection;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *identifier = #"Cell";
UICollectionViewCell *cell =
[collectionView dequeueReusableCellWithReuseIdentifier:identifier
forIndexPath:indexPath];
UIImageView *recipeImageView = (UIImageView *)[cell viewWithTag:100];
recipeImageView.image = [imageArray objectAtIndex:
(indexPath.section * noOfSection + indexPath.row)];
return cell;
}
In your xib file add UIImageView to your CollectionViewCell and change it tag value to 100.
Please go through below links.
http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UICollectionView_class/Reference/Reference.html
Above link is from Apple developer site. It has all details of UIcollectionview and tutorials related it. Below URL is also having sample tutorial, which will help you a lot.
http://www.raywenderlich.com/22324/beginning-uicollectionview-in-ios-6-part-12
You have images array retrieved from database. Now it will act as data source for collection view. You need to form UICollectionViewCell which will have those images accordingly. Please study above links, you will get to know.