I need to allow my users to scroll through a list of thumbnails in the documents directory on an iPad application. When they tap on one of the thumbnails, I need to know which one was tapped (or have it call a method, which is basically the same thing.)
I know how to put the thumbnails in the documents directory, and how to read them from there. But I’m not sure which widget is best for displaying the content. Is it UITableView? CCScrollLayer? CCMenuAdvanced? Something else?
CCScrollLayer doesn't work as well as UIScrollView.
I use the view to CCdirector for add UIkit.
UIScrollView *scrollView = [UIScrollView alloc] init];
[[[CCDirector sharedDirector] view] addSubview:scrollView];
or add UITableView.
Take all the paths for thumbnails in one array, lets call pathsArray
NSArray *pathsArray = [seld getPathsForAllImages];
Now implement UITableViewDataSource and UITableViewDelegate:
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [pathsArray count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
}
NSString *pathForImage = [pathsArray objectAtIndex:indexPath.row];
cell.imageView.image = [UIImage imageWithContentsOfFile:pathForImage];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *pathForImage = [pathsArray objectAtIndex:indexPath.row];
}
I've had all kinds of problems with overlaying a UIScrollView on a cocos2d project. Even with all the suggested hacks, if a single frame takes too long in cocos2d, the UIScrollView just stops working right. I would suspect that would happen with a UITableView as well.
To solve my own problems, I created a custom ScrollNode class. It's really easy to use:
// create the scroll node
ScrollNode *scrollNode = [ScrollNode nodeWithSize:CGSizeMake(size.width, size.height)];
[self addChild:scrollNode];
// Generate some content for the scroll view
// add it directly to scrollView, or to the built in menu (scrollView.menu)
[scrollNode.menu addChild:yourMenuItem];
// Set the content rect to represent the scroll area
CGRect contentRect = [scrollNode calculateScrollViewContentRect];
[scrollNode setScrollViewContentRect:contentRect];
Related
To sum it up, I'm facing 2 problems, the 1st is that time to time some cells disappear from the tableview and reappear randomly. The 2nd is a scrolling performance issue on iPhone 4 and below.
1st problem :
I am displaying a simple list of elements in an UITableView by adding a subview the [cell contentView]. Most of the time it works well, but sometimes when i scroll (and really randomly) a cell disappears.
Because this cell which "disappeared" is reused, the reused cell will also be "blank" when I scroll. And randomly again a cell can reappear/disappear while scrolling.
Here is my code for the cellForRowAtIndexPath: method :
- (UITableViewCell *)tableView:(UITableView *)_tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [_tableView dequeueReusableCellWithIdentifier:CellIdentifier];
LBSelectorChampionViewController *item = [self itemAtIndexPath:indexPath];
if (cell == nil) {
NSLog(#"CELL NIL");
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
[[item view] setTag:99];
[[item champName] setTag:50];
[[item champImage] setTag:51];
[[item buttonFavorite] setTag:52];
[[cell contentView] addSubview:[item view]];
}
UILabel *championLabel = (UILabel*)[[[cell contentView] viewWithTag:99] viewWithTag:50];
LBFavoriteChampionButton *buttonFavorite = (LBFavoriteChampionButton*)[[[cell contentView] viewWithTag:99] viewWithTag:52];
UIImageView *championImage = (UIImageView*)[[[cell contentView] viewWithTag:99] viewWithTag:51];
// Text
[championLabel setText:[item displayValue]];
[championImage setImageWithURL:[NSURL URLWithString:[item imageUrl]]];
// Fav button
[buttonFavorite setCurrentChampion:item];
if ([[self favoritesChampions] containsObject:item]) {
[buttonFavorite setSelected:YES];
[buttonFavorite setIsFavorite:YES];
} else {
[buttonFavorite setSelected:NO];
[buttonFavorite setIsFavorite:NO];
}
return cell;}
}
I've tried to log everything, I checked if "item" could be sometimes null and it's never the case. But when a cell disappears, the [[[cell contentView] subviews] count] is equal to 0, which normally should be 1.
I really don't understand what's happening here. I tested it on real devices + simulator and it happens with both of them.
2nt problem :
My other "problem" is that the scrolling of the tableview is not as smooth as i would expect it to be on the iPhone 4 (tested on iOS 6 and iOS 7, same result :-( ). I'm using SDWebImage to load images asynchronously and to cache them, I'm reusing cells, what could I do to improve the performances ? I tested on an iPhone 4S and had no problem, scrolling is very smooth.
Am I doing something wrong ? Any ideas about one of my problem ?
Thank you for taking the time to answer me.
EDIT : 1st attempt to solve the problem
Trying to customize cell with a custom UITableViewCell subclass :
- (UITableViewCell *)tableView:(UITableView *)_tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"LBListChampionCell";
LBListChampionCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil){
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"LBListChampionCell" owner:self options:nil];
cell = [topLevelObjects objectAtIndex:0];
}
LBSelectorChampionViewController *item = [self itemAtIndexPath:indexPath];
[[cell championName] setText:[item displayValue]];
[[cell championLogo] setImageWithURL:[NSURL URLWithString:[item imageUrl]]];
return cell;
}
I wasn't able for the moment to reproduce the "disappear" bug (sometimes it takes a moment to reproduce it...), but there is no improvement at all concerning the performances :(. Even while just setting a dummy text : [[cell championName] setText:#"Test"]; (and commenting the item part) the scrolling is still not really smooth.
Any ideas ?
EDIT 2 : Best solution (thanks to rdelmar)
Create a subclass of UITableViewCell and load the nib in viewDidLoad :
- (void)viewDidLoad
{
...
UINib *nib = [UINib nibWithNibName:#"LBListChampionCell" bundle:nil];
[[self tableView] registerNib:nib forCellReuseIdentifier:#"LBListChampionCell"];
}
- (UITableViewCell *)tableView:(UITableView *)_tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
LBListChampionCell *cell = [tableView dequeueReusableCellWithIdentifier:#"LBListChampionCell"];
LBSelectorChampionViewController *item = [self itemAtIndexPath:indexPath];
[[cell championName] setText:[item displayValue]];
[[cell championLogo] setImageWithURL:[NSURL URLWithString:[item imageUrl]]];
...
}
It increased the scrolling performance (still not perfectly smooth on iPhone 4 but it works well). Moreover it seems that no cell are disappearing anymore :-)
Thanks rdelmar!
I think somehow LBSelectorChampionViewController *item = [self itemAtIndexPath:indexPath]; from one of your cell's contentView was added to another cell's contentView, which would cause the previous cell's contentView to have 0 subview.
I think you are getting the first problem because of cell reusability. LBSelectorChampionViewController *item = [self itemAtIndexPath:indexPath]; will not always be added to the [cell contentView] as cell may not be nil. In addition, there might be a situation when this view will be added to another cell and removed from the previous. Anyway, this will be easier to fix if you subclass UITableViewCell and make LBSelectorChampionViewController part of this custom cell. This will make everything easier and you will never meet reusability problems.
I am not exactly familiar with SDWebImage, but maybe the problem is that it doesn't cache images and tries to load new image every time [championImage setImageWithURL:[NSURL URLWithString:[item imageUrl]]]; is hit. I've used AFNetworking in a few projects to load images and have never had any problems with it.
Hope this is helpful!
Cheers!
This is for a blog app and the issue I'm having is with the layout of the post and its comments.
Ideally the layout would be like this (version 1):
The parent is a UIScrollView, and all other elements you see are inside it. The UITabelView at the bottom receives the comment thread.
This works, but the problem is with the comments UITableView. I thought I could turn off its scrolling and have it "grow" vertically based on the number of comments. But after a few rows, the content gets clipped. That is because the remaining rows are not being rendered within the vertical size of the UIScrollView.
I've read in SO to do away with the UIScrollView - and instead build the whole thing in a UITableView. Create a custom cell on top to hold the UIImageView, another custom cell below for post text, etc.
Version 2
My question is: how do you send a specific portion of a JSON feed to populate custom cells, and then iterate through the comments populating the lower cells?
For example the code below sends/iterates comments to the comments UITableView in version 1.
But how would I send the post image or post text to the custom cells in version 2?
Is there a better approach? Any kind of advice is greatly appreciated.
DetailViewController.h
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"commentCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
NSDictionary *post = self.detailItem;
NSArray *commentThread = [post objectForKey:#"comment"];
NSDictionary *comment = [commentThread objectAtIndex:indexPath.row];
NSString *commentText = [comment objectForKey:#"comment_text"];
NSString *commentAuthorName = [comment objectForKey:#"comment_author_name"];
cell.textLabel.text = commentText;
cell.detailTextLabel.text = commentAuthorName;
return cell;
}
JSON
...
{
"post_id": "1463",
"post_title": null,
"post_text": "dffsdjdflkjklk dlfkjsdlfkj",
"comment": [
{
"comment_author_name": "2162",
"comment_text": "yuiyuiiopiop",
},
{
"comment_author_name": "2163",
"comment_text": "tyutyutyutyuu",
},
{
"comment_author_name": "2164",
"comment_text": "sdfsertertr",
},
]
},
...
you can put post text and image into tableViewHeader.
or put the post text and image in tableView section 0
put the comment in section 1
Edit
{
[super viewDidLoad];
UIImageView *imageView = [[UIImageView alloc] init];
//config the image view
UILabel *postContentLabel = [[UILabel alloc] init];//or other UI component
//Config the postContentLabel
UIView *tableHeaderView = [[UIView alloc] init];
[tableHeaderView addSubview:imageView];
[tableHeaderView addSubview:postContentLabel];
//set the tableHeaderView frame
self.tableView.tableHeaderView = tableHeaderView;
}
yeah it is correct either you simply use header view in table view i m putting the example
take a view say headerView and have imageView in it
after response simply put the image in a imageview object
then load the table view like that
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
return self.headerView;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 210;
}
then use the cellForRowAtIndexPath as you have used above .
In my application i need to display multiple images in a UITableView,so i've already searched a lot in the web for the proper way to load large images to UITableViewCells,to be more clearly i'll divide the procedure that my app execute:
Download images asynchronous;
Save them to NSHomeDirectory();
=> Thins part is working perfectly.
The problem is,how to display the images in the UITableViewCell,i've already tried to add UIImageView's to the cell contentView but the scrolling performance were a bit affected,i've searched on Apple guides and i believe the correct way is adding UIImageView's to the cell and loading the images from NSHomeDirectory(),so:
What's the best way to customize a UITableViewCell and add the UIImageView's(302x302px) to it?
To get the best scroll performance, you must draw the content of the UITableViewCell yourself.
Loren Brichter, the author of the Tweetie app (now the official Twitter app), wrote a very famous blog post on this. Sadly, this blog post has been deleted.
This article may help you, though. It explains the fast scrolling, it has examples and it has a video to a presentation from Loren Brichter.
Basically, what you want to do is to subclass UITableViewCell and override the drawRect:method. To show an image, you would do something like the following:
- (void)drawRect:(CGRect)rect
{
[myImage drawAtPoint:CGPointMake(10, 10)];
}
This way you avoid to layout a lot of subviews.
I have the same question.
I'm doing the following:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString* CustomCellIdentifier = #"CustomCellIdentifier";
CustomCell* cell = [tableView dequeueReusableCellWithIdentifier:CustomCellIdentifier];
if (cell == nil) {
cell = [[CustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CustomCellIdentifier];
}
// add subview to cell
if (cell.customView == NULL) {
cell.customView = [[CustomView alloc] initWithFrame:cell.frame];
[cell.contentView addSubview:cell.customView];
}
// bind cell data
return cell;
}
Firstly, you need to create a custom cells for UITableView & keep going through following points.
set height of each row to 302 pixels as
-(CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 302.0;
}
Use following code to create UIImageView at each cell of table
-(UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:#"cell"];
const NSInteger IMAGE_VIEW_TAG=1001;
UIImageView *imageView;
if(cell==nil)
{
cell=[[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"cell"] autorelease];
cell.accessoryType=UITableViewCellAccessoryDisclosureIndicator;
imageView =[[[UIImageView alloc] initWithFrame:CGRectMake(10, 0, 302, 302)] autorelease];
imageView.tag=IMAGE_VIEW_TAG;
imageView.contentMode=UIViewContentModeScaleAspectFit;
[cell.contentView addSubview:imageView];
}
imageView=(UIImageView*)[cell.contentView viewWithTag:IMAGE_VIEW_TAG];
[imageView setImage:[UIImage imageNamed:#"image.png"];
return cell;
}
set number of rows you want to display
-(NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 5;
}
dont forget to add TableView delegate and data source , UITableViewDelegate and UITableViewDataSource
When I scroll down and up again my text in tableView will disappear.
And my code is:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [screenDefBuild.elementsToTableView count];
}
- (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];
}
ScreenListElements *currentScreenElement = [screenDefBuild.elementsToTableView objectAtIndex:indexPath.row];
cell.textLabel.text = currentScreenElement.objectName;
currentRow++;
return cell;
}
- (void)viewDidLoad
{
[super viewDidLoad];
tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
[tableView setDataSource:self];
[self.view addSubview:tableView];
}
I also want to fill my table view to entire screen. (grey strap on the top).
I don't know what you're doing with this variable
currentRow++;
But whatever you use that for, i'd wager its breaking your code.
the UITableView will call cellForRowAtIndexPath every time a cell is about to appear on screen regardless of whether it has been on screen before or not. When you scroll down and then scroll back up this variable will have increased beyond the bounds of your data, hence you get empty cells.
You need to design this method in such a way that it can create any cell in the table view at any time. You can't rely on the order that the cells will be made and with scrolling you will have to make the same cell over and over again. Only use indexPath to figure out which cell you are currently supposed to be making.
http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UITableView_Class/Reference/Reference.html
Answer for the second part of the question- grey strap. You are adding the table view to current view, so you should use the size property of self.view.frame but not the origin. You want this to be set to 0,0.
Change
tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
to
CGRect viewFrame=self.view.frame;
viewFrame.origin=CGPointZero;
tableView = [[UITableView alloc] initWithFrame:viewFrame];
As for the first part of your question- it's strange, as you seem to do everything properly. One thing i may suggest is to add [tableView reloadData]; in the end of viewDidLoad.
I'm loading pictures into a table view that correspond to the cell text, so each image is different. As the user scrolls down, iOS has to manually reload each image from the SSD, so scrolling is very choppy. How do I cache images or prevent the table view cells from needing to be recreated? Others have had their issues solved by using imageNamed: as iOS will automatically cache your images, but I am loading images from the documents directory, not the app bundle. What should I do? Thanks.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return [issues count];
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}
// Set up the cell...
NSDictionary *dic = [self.issues objectAtIndex:indexPath.row];
cell.text = [dic objectForKey:#"Date"];
cell.imageView.image = [UIImage imageWithContentsOfFile:[NSString stringWithFormat:#"%#/issues/%#/cover.png", documentsDirectory, [dic objectForKey:#"Directory Name"]]];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 150;
}
That array of dictionaries is your model, so it would be a natural place. The tricky part is making your model mutable. You can either make your model mutable in the class, or jump through the hoops when you cache the image. I'd recommend the former, but coded it here to match your existing code...
- (UIImage *)imageForIndexPath:(NSIndexPath *)indexPath {
NSDictionary *dic = [self.issues objectAtIndex:indexPath.row];
UIImage *image = [dic valueForKey:#"cached_image"];
if (!image) {
image = [UIImage imageWithContentsOfFile:[NSString stringWithFormat:#"%#/issues/%#/cover.png", documentsDirectory, [dic objectForKey:#"Directory Name"]]];
NSMutableDictionary *updatedDictionary = [NSMutableDictionary dictionaryWithDictionary:dic];
[updatedDictionary setValue:image forKey:#"cached_image"];
NSMutableArray *updatedIssues = [NSMutableArray arrayWithArray:self.issues];
[updatedIssues replaceObjectAtIndex:indexPath.row withObject:[NSDictionary dictionaryWithDictionary:updatedDictionary]];
self.issues = [NSArray arrayWithArray:updatedIssues];
}
return image;
}
This will be choppy on the first scroll through the list, then smoother thereafter. If you'd like to have no first-time chop and incur a little latency before the view appears, you can walk your model ahead of time and force the load:
for (int i=0; i<self.issues.count; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
(void)[self imageForIndexPath:indexPath];
}
One last thing - Another solution is a mutable array to match your issues array. That may be just fine, but more often than not, I end up glad that I included some cached calculation with my model. If you find yourself using that issues array elsewhere, you'll be happy that you have the images all taken care of.
Along with caching you may also consider loading the images in background using Grand central dispatch. When the cell is loaded put a UIActivityIndicator then replace it with an image in a separate thread.
Also checkout this related answer for image stutter:
Non-lazy image loading in iOS