I have create a labels with in the scroll view. The for loop count is more than 1000. At the time the scroll is very slow. If the data is less amount (200, 300, ...) at the time its scroll smoothly. I am using the below code for create label.
UILabel *modelLabel;
UIButton *modelButton;
for (int i = 0; i < [modelArray count]; i++)
{
modelButton = [UIButton buttonWithType:UIButtonTypeCustom];
modelButton.frame = CGRectMake(0.0, (i * LABEL_HEIGHT), LABEL_WIDTH, LABEL_HEIGHT);
modelButton.tag = i+100;
[modelButton addTarget:nil action:#selector(modelButtonAction:) forControlEvents:UIControlEventTouchUpInside];
[modelScrollView addSubview:modelButton];
modelLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0, (i * LABEL_HEIGHT), LABEL_WIDTH, LABEL_HEIGHT)];
modelLabel.text = [NSString stringWithFormat:#"%#", [[modelArray objectAtIndex:i] valueForKey:#"Model"]];
modelLabel.tag = i+1000;
modelLabel.backgroundColor = [UIColor clearColor];
modelLabel.textColor = [UIColor grayColor];
modelLabel.alpha = 0.5;
modelLabel.textAlignment = UITextAlignmentCenter;
modelLabel.font = EUROSLITE_FONT(14);
[modelScrollView addSubview:modelLabel];
}
[modelScrollView setContentSize:CGSizeMake(280.0, ([modelArray count] * LABEL_HEIGHT))];
How can I fix this issue?
Thanks in advance.
This happens to you because you load to many things to the memory
As the comment above said, It will be very easy to achieve using UITableView.
Although if you want more control, and you decide using scroll view you have to implement lazy loding. This is being done be allocating and placing the labels when it actually needed to be showen and not during the initialization. You will be able to know that by setting you view controller as the delegate and get the contentOffset from the scrollViewDidScroll: method. Also after recognizing you'll need te remove the subview in order to clear the memory
You can see good example for it here
I feel its because ios is trying to create and keep alot of these views in memory rather then create them dynamically(am I using that word right?) as they scroll on. It would be alot faster to use a UITableView that has a reuse identifier. This will stop iOs from creating a new allocation of memory for each view you make. Create a custom UTTableViewCell, with the labels and buttons you need that is created via a reuse identifier.
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
You should be able to find alot of resource on the web for creating a UITableView with a custom style. If you need a horizontal scrolling UITableView take a look at EasyTableView.
Related
I am new to iPhone programming.
Using below code I can able to download and displaying all images form server. But in server I have more than some 1000s of images are there. so Using below code I can able to download and displaying in scrollview as 3*3 thumbnail.
But what I want means first I have to download and display 15 images in scrollview as 3*3 thumbnail.
If I scroll down means i have to show activity indicator then download next form 16 to 30 images, similarly again if I scroll means I want to download and display 31 to 45 images in thumbnail.
I dont want to download all images form server.
Can any tell me please how can I do this.
- (void)viewDidLoad
{
URLs = [[NSMutableArray alloc]init];
for (NSString *path in latestiamge)
{
NSURL *URL = [NSURL URLWithString:path];
if (URL)
{
[URLs addObject:URL];
}
else
{
NSLog(#"'%#' is not a valid URL", path);
}
}
self.imageURLs = URLs;
myScrollView = [[UIScrollView alloc]initWithFrame:CGRectMake(0.0, 84.0, 320.0, 840.0)];
myScrollView.delegate = self;
myScrollView.contentSize = CGSizeMake(320.0, 840.0);
myScrollView.backgroundColor = [UIColor whiteColor];
[self.view addSubview:myScrollView];
float horizontal = 2.0;
float vertical = 2.0;
for(int i=0; i<[imageURLs count]; i++)
{
if((i%3) == 0 && i!=0)
{
horizontal = 5.0;
vertical = vertical + 100.0 + 5.0;
}
CGRect frame;
frame.size.width=100.0;
frame.size.height=100.0;
frame.origin.x=0;
frame.origin.y=0;
AsyncImageView *imageView = [[AsyncImageView alloc] initWithFrame:frame];
imageView.contentMode = UIViewContentModeScaleAspectFill;
imageView.clipsToBounds = YES;
imageView.tag = i;
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc]
initWithTarget:self
action:#selector(actionHandleTapOnImageView:)];
imageView.userInteractionEnabled = YES;
[imageView addGestureRecognizer:singleTap];
[myScrollView addSubview:imageView];
[myScrollView addSubview:imageView];
horizontal = horizontal + 100.0 + 5.0;
}
[myScrollView setContentSize:CGSizeMake(320.0, vertical + 3900.0)];
[super viewDidLoad];
}
Well there is a good and basic class you can use for downloading and displaying images asynchronously .
SDWebImage
Doing everything yourself as you currently are is more effort than is required and not good for memory management. Consider using a table or collection view to manage the scrolling so you don't have so many views loaded at the same time and so you don't need code for the full layout of everything.
You're already using AsyncImageView, it will work if you add a number of them to the cells you're going to display and configure them as requested by the delegate/dataSource methods.
You should also think about acting as the scroll view delegate and monitoring the scroll completion. If the user has scrolled to the current bottom, you could add a footer view with an activity indicator, start a load of the next page from the server and then reload the view and remove the footer when the new page is downloaded.
From what I can understand from your question, you want to create a image grid with three images in a row. But your approach is wrong...!
Do not use scrollView but use a UITableView. I think you can use UICollectionView if you are targeting iOS 6.0 or above.
Here is what you need to do :
Create custom UITableViewCell with number of images you need in a row. You can make this dynamic too by passing number of grid items you need while creating the cell and creating the and positioning those views in the cell as subviews.
Reuse the cells in the table to populate the grid.
You can cache images for better performance, I would suggest you to use SDWebImage
In cellForRowAtIndexPath you can configure all the gridItems.
Do you need more help...??
I'm currently working on an iPhone app that's doing some strange things with a UIScrollView inside a UITableView. This is my first foray into iPhone dev, so I'm sure it's something silly I'm missing.
In each UITableViewCell I am putting in a basic UITableViewCell. In that UITableViewCell is a Label and a UIScrollView.
The label and scrollview is setup and working properly, but when it first displays it is offset about 30 pixels down on the y axis than it should be, or is positioned by the XIB/NIB. I am not moving it around manually. The label shows up in the right spot. at 0,0. The UIScrollView should be showing up at 0,22 but is showing up closer to 0,40.
When I swipe to scroll the containing UITableView, then all the UIScrollViews will show up in the right spot assuming that when the UITableView scrolled that UITableViewCell went offscreen.
Here is the code for the UITableView.cellForRowAtIndexPath
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"GalleryRowCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
[cell.layer setMasksToBounds:TRUE];
[cell.layer setCornerRadius:10.0];
Contagion *c = (Contagion *)[self.dataSet objectAtIndex:indexPath.row];
GalleryRowViewController *subView = [[GalleryRowViewController alloc] initWithContagion:c];
[cell.contentView addSubview:subView.view];
subView.contagionName.text = c.name;
subView.contagion = c;
return cell;
}
Here is the code for my GalleryRowViewController.viewDidLoad
- (void)viewDidLoad {
[super viewDidLoad];
self.imageScroll.delegate = self;
[self.imageScroll setBackgroundColor:[UIColor blackColor]];
[self.imageScroll setCanCancelContentTouches:NO];
self.imageScroll.indicatorStyle = UIScrollViewIndicatorStyleWhite;
self.imageScroll.clipsToBounds = NO;
self.imageScroll.scrollEnabled = YES;
self.imageScroll.pagingEnabled = NO;
NSInteger x = 0;
CGFloat xPos = 0;
for (x=0;x<=10;x++) {
UIImage *image = [UIImage imageNamed:#"57-icon.png"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
[imageView setBackgroundColor:[UIColor yellowColor]];
CGRect rect = imageView.frame;
rect.size.height = 70;
rect.size.width = image.size.width;
rect.origin.x = xPos;
rect.origin.y = 5;
imageView.frame = rect;
[self.imageScroll addSubview:imageView];
xPos += imageView.frame.size.width+5;
}
[self.imageScroll setContentSize:CGSizeMake(xPos, [self.imageScroll bounds].size.height)];
}
--- EDIT FOR IMAGES ---
After App Loads: http://img809.imageshack.us/img809/4576/screenshot20110927at427.png
After Scrolling the rows offscreen and back: http://img690.imageshack.us/img690/9461/screenshot20110927at428.png
Well, as my previous response was at too low a level, let me take another shot at it.
First, I just noticed the core problem that you're using a viewcontroller for each cell. To quote Apple, " "A single view controller typically manages the views associated with a single screen’s worth of content." That would also get rid of your XIB (just manually configuring your scrollview), which I bet will get rid of your problem.
To proceed, your main choice is whether to create a ContagionTableViewCell class or not as suggested by Scott.
If so, following the Elements example, create a subclass of UITableViewCell ContagionTableViewCell with properties of a scrollView, a labelview and a contagion. Like they use a custom setter for the element, use one for the contagion, so that whenever it is assigned, it also updates the cells label (and associated pictures).
Move your imageScroll code from GalleryRowViewController.viewDidLoad into the ContagionTableViewCell init code. Put the image code into a new routine, which will be called from the contagion setter.
If NOT, then move the GalleryRowView Controller code into your UITableView. I suggest you take a look at cellForRowAtIndexPath in Apple's tableViewSuite, the fourth example on subviews. In particular, it shows this pattern of separating the creation of a cell (when you need a brand new one) vs configuring the cell (when reusing it). As you have 10 imageViews inside your scrollView, you'll have to decide whether to delete all those (and/or the scrollview), or just reach inside and update their images when a cell is reused.
Can you post a screenshot. Its a bit hard to visualize what you are describing. I'm not sure how you are computing y origin to be 22.
As a side note I believe its cleaner to do this by creatint your own TableViewCell subclass and use that instead of the default UITableViewCell. There is an example called Elements which shows how to do this properly: http://developer.apple.com/library/ios/#samplecode/TheElements/Introduction/Intro.html
Well, I don't know it's the cause of your problem, but you've definitely got an issue. Note that every time you are asked for a cell, you're adding the galleryRow subview. When a cell goes off-screen, it's put on the reusableCell queue. Then you're asked for another cell; you get it from the queue, it still has the old galleryRow subview, and now you add another one; so that's not good. You should either reuse or delete the old one.
Finally, why are you using UITableViewCellStyleSubtitle, and then not using any of the default fields in that UITableView?
Hi am curious how I would go about implementing a view like (See below) Similar to the one used for the iPhone photo application. My initial idea was a table view with each cell holding 4 images, can anyone point me in the right direction. I am particularly interesting in using APIs from Apple so not too bothered about 3rd party APIs / Controllers at this stage.
Yup, as you say, a table view would work, with each of the 4 things (/row) being an UIButton with a custom image. You have to make sure that the table row itself isn't selectable though. (UITableView Setting some cells as "unselectable") Make sure to use table view cell caching if you're going to have a lot of rows.
The actual table view cell could be a UITableViewCell subclass or just the normal one, with some subviews added to its contentView.
I would suggest Three20 API. This is the API used to build the Facebook iPhone app and has a very very large collection of UI elements, including many photo management UIs.
The thumbnail viewer looks like (from their site):
On the iOS, this work is mostly done for you. Check Apple's docs for MPMediaPickerController.
AQGridView is FOSS and is used in many high profile applications like the Kobo reader and Netflix Actors.
You need to use UIScrollView and with a bunch of UIImageViews or UIButtons(with image as background image). this code uses Buttons on scroll view. Also u wud need a function to generate thumbnails, unless ur using the asset library in which case the function is already there.
[scrollview setContentSize:CGSizeMake(320, (items*itemWidth)];
for (int i = 0; i < [items count]; i++) {
if (i % 4 == 0) {
y+=1;
x = 0;
}
UIButton *myButton = [UIButton buttonWithType:UIButtonTypeCustom];
[myButton setBackgroundImage:[UIImage imageWithCGImage:[items objectAtIndex:i]] forState:UIControlStateNormal];
CGRect rect2 = myButton.frame;
rect2.size.height = kScrollObjHeight2;
rect2.size.width = kScrollObjWidth2;
rect2.origin.y = 10 + (y * 70) + (y *5);
rect2.origin.x = 8 + (x * kScrollObjWidth2) + (x *8);
myButton.frame = rect2;
myButton.tag = i; // tag our images for later use when we place them in serial fashion
x = x + 1;
[myButton addTarget:self action:#selector(clickFunction:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:myButton];
[scrollview addSubview:myButton];
}
};
[scrollview setBackgroundColor:[UIColor blackColor]];
[scrollview setCanCancelContentTouches:NO];
scrollview.indicatorStyle = UIScrollViewIndicatorStyleWhite;
scrollview.clipsToBounds = YES; // default is NO, we want to restrict drawing within our scrollview
scrollview.scrollEnabled = YES;
scrollview.pagingEnabled = YES;
I have an NSMutableArray of custom views (that are pretty much 1 UITextView and 1 UILabel with a custom back ground image), these are created as they are need (I start with 2 (though the first one is only 2 UITextFields and the other is normal) of these). Now my issue here seems to be this: as soon as I try to edit any UITextView past the one in the 2nd view, it starts to run incredibly slow, not the app, just the textview. For example, as I type, the little blinky guy lags behind the text and when I click to copy/paste/cut/etc you can see the little balloon fly in from the upper left corner every time. I have run the static analyzer for leaks and come up with nothing and run it alongside some other the testing software in XCode and it does not appear to have any reason for this.
Any help would be greatly appreciated.
EDIT Adding code
Custom UIView, all that's really called on each
-(id)initWithFrame:(CGRect)frame andIdentifier:(NSInteger)_t {
if ((self = [super initWithFrame:frame])) {
// Initialization code
self.image = [UIImage imageNamed:#"page.png"];
self.tag = _t;
self.userInteractionEnabled = YES;
self.clipsToBounds = YES;
drawImage = self.image;
text = [[UITextView alloc] initWithFrame:CGRectMake(40.0, 40.0, self.frame.size.width - 80.0, self.frame.size.height - 80.0)];
text.delegate = self;
Lbl = [[UILabel alloc] initWithFrame:CGRectMake(self.frame.size.width - 30, self.frame.size.height - 30, 20, 20)];
Lbl.text = [NSString stringWithFormat:#"%d",self.tag];
[self addSubview:text];
[self addSubview:Lbl];
[Lbl release];
}
return self;
After that, I store the view in an array in the view controller and then stack 2 (used to be 3) at a time with one front and foremost and one below it. These get shuffled a bit but thats about it. I do not think it is a device issue, just a weird error that I cannot seem to catch.
found my problem, apparently some obscure animation block got left opened, which apparently animated everything else that was going on on the UIKit side. odd but makes sense
Check out the last lines before [return cell]..After the images are being loaded the scrolling speed is decreasing..it seems the scroll gets stuck
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:MyIdentifier] autorelease];
}
int storyIndex = [indexPath indexAtPosition: [indexPath length] - 1];
NSString *itemDescription=[[stories objectAtIndex: storyIndex]
objectForKey:#"title"];
CGRect aframe = CGRectMake(80, 30, 250, 40);
textLabel = [[UILabel alloc] initWithFrame:aframe];
textLabel.font = [UIFont boldSystemFontOfSize:14];
textLabel.numberOfLines=0;
textLabel.textColor = [UIColor darkTextColor];
textLabel.backgroundColor = [UIColor whiteColor];
[cell.contentView addSubview:textLabel];
textLabel.text=itemDescription;
CGRect frame = CGRectMake(0, 0, 70,80);
UIImageView *TopImageView = [[UIImageView alloc] init];
[cell.contentView addSubview:TopImageView];
TopImageView.frame=frame;
m_strImage = [m_imglinkArray objectAtIndex:storyIndex];
TopImage = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:m_strImage]]];
TopImageView.image=TopImage;
return cell;
}
Could you guys help me to increase the speed of the scroll?
Regards
Arun
I believe you may have two problems here. First is the issue of not multi-threading the image loads and second is how you're using UITableViewCell.
iPhone calls cellForRowAtIndexPath whenever a cell appears on the screen - if you scroll down, everything above is unloaded and reloaded when you scroll back up again. As already noted this can be solved with multithreading patterns - either the link posted to markj.net, and/or by using NSThread. The advantage of NSThread is you have more flexibility to handle problems or load more than just image data. (I would use both)
In order to do this efficiently you need to have a custom UITableViewCell. This is good to have regardless because you can make the table cell responsible for it's content rather than your view controller, and you can efficiently manipulate cell formatting and content. Here is a link to a great post about UITableCellView: http://blog.atrexis.com/index.cfm/2009/1/6/iPhone--Customize-the-UITableCellView
When you implement UITableViewCell, do your best to put all addSubView and formatting calls in the "if(cell==nil)" block, only if you can't put them in your custom initWithFrame. You'll find the iPhone is stacking every subview you add -- it's not clearing the old view. You can see this at work by setting all the background colors to "UIColor clearColor" and then change text between hits -- you'll see a stack of text on top of each other. This isn't normally visible because solid-filled backgrounds draw over all the "old" subviews.
Before you combine these two methods, considering implementing a "model". You've got a view and a controller but all of your data should be in a model. All of the image URLs, content, etc. should be in something like an NSMutableArray, a custom object of your own, or maybe via SQLite. Once you have a model, you can implement your caching of the images. With caching, all of the images will be retained between loads of your application which will save batteries, bandwidth, and time.
I would:
1. put all your data in a model of some kind (NSMutableArray, SQLLite, something)
2. Implement your own custom UITableViewCell
3. Make sure you have 0 addSubView, init, or alloc calls inside the active block of cellForRowAtIndexPath
4. Use delegation example or NSThread to load all images in the background
5. Add local image caching
There are some good examples out in about and on the Apple forums on caching images.
nessence has a lot of good information. A few more thoughts:
You're leaking like crazy here. Every cell you create, you leak 2 UILabels, a UIImageView and a UIImage.
As noted before, not only are you leaking these, they're accumulating in your view because you're sticking one on top of the other with addSubview:.
Reaching out to the network during a cell draw is incredibly slow and is blocking your UI. If these URLs are local, then you can use UIImage's +imageWithContentsOfFile, if not, you need to load this in the background.
I don't think you need a thread here. NSURLConnection is an excellent way to load data in the background without incurring the overhead of threading.
nessence is completely correct that you need a model class for Story.
Your basic approach to reusable cell configuration is incorrect. You don't fetch a reusable cell and then add subviews to it. All your subviews should be added in the if() block to create the cell. Then, in each pass, you just change the values of things. I've rewritten some of your code below to demonstrate. This is still not correct code, because it is reaching out to the network during a cell draw, and this may be too many elements to be in a subview-cell (rather than a custom cell), but it's closer to the right idea. I don't even know if this compiles; I just typed it here.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
// Here we do all our cell creation
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:MyIdentifier] autorelease];
// Make the label
CGRect aframe = CGRectMake(80, 30, 250, 40);
UILabel *textLabel = [[[UILabel alloc] initWithFrame:aframe] autorelease]; // Note the -autorelease so we don't leak
textLabel.font = [UIFont boldSystemFontOfSize:14];
textLabel.numberOfLines=0;
textLabel.textColor = [UIColor darkTextColor];
textLabel.backgroundColor = [UIColor whiteColor];
textLabel.tag = DescriptionTag; // A tag so we can find it later (you'll need a constant for this)
[cell.contentView addSubview:textLabel];
// And the second label
aframe = CGRectMake(80, 30, 250, 40);
textLabel = [[[UILabel alloc] initWithFrame:aframe] autorelease];
textLabel.font = [UIFont boldSystemFontOfSize:14];
textLabel.numberOfLines=0;
textLabel.textColor = [UIColor darkTextColor];
textLabel.backgroundColor = [UIColor whiteColor];
textLabel.tag = TitleTag;
[cell.contentView addSubview:textLabel];
// The image view
CGRect frame = CGRectMake(0, 0, 70,80);
UIImageView *topImageView = [[[UIImageView alloc] init] autorelease];
topImageView.frame = frame;
topImageView.tag = TopImageTag;
[cell.contentView addSubview:topImageView];
}
// all the above was cell creation; we do that as seldom as possible.
// Now we do cell configuration. We want this to be fast.
UILabel *descriptionLabel = (UILabel*)[cell.contentView viewWithTag:DescriptionTag];
descriptionLabel.text = itemDescription;
UILabel *titleLabel = (UILabel*)[cell.contentView viewWithTag:TitleTag];
titleLabel.text =[[stories objectAtIndex:indexPath.row] objectForKey:#"title"];
NSString *imageURLString = [m_imglinkArray objectAtIndex:storyIndex]; // You should have a model class called Story, not two arrays.
UIImage *image = [[[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageURLString]]] autorelease]; // This is still way too slow if it's a remote URL
UIImageView *imageView = (UIImageView*)[cell.contentView viewWithTag:TopImageTag];
imageView.image = image;
return cell;
}
I recommend you spend some quality time studying TableViewSuite, Practical Memory Management, and Coding Guidelines for Cocoa. Some time studying the basics of Cocoa would be useful as well, because the coding style here indicates that you may not have a solid foundation. Though it's a Mac book, I still recommend Cocoa Programming for Mac OS X. If you're interested in using this to learn iPhone, I've put together a syllabus that might help. I haven't reviewed it yet, but Stanford's online CS193P course looks promising.
this has been asked several times here, what you need to do is to load asynchronously the images for each cell in order to prevent the scroll to slow down.
This is called "Lazy image loading" and I'm referencing the apple tutorial here :
Lazy load images in UITableView
It seems that you are loading from a url every time the table view asks to get the cell for a row. This is happening in the main application thread (the User interface thread) and it blocks the user interface, making it not responsive.
Especially if this url is a resource that you load from the internet then this pause is big.
I would recommend that you should load the images in a background thread and display a placeholder image until they are fetched from the network.
You can try the solution provided in the following url...
http://www.markj.net/iphone-asynchronous-table-image/