I have added the images to scrollview dynamically. Following is the code:
-(void)displayimages {
for (int i=0; i<[featuredarray count]; i++) {
NSString *image=[[featuredarray objectAtIndex:i]objectForKey:#"img"];
if (i==0) {
webview=[[UIImageView alloc]initWithFrame:CGRectMake(i*290+15, 0, 270, 380)];
}
else {
webview=[[UIImageView alloc]initWithFrame:CGRectMake(i*290+15, 0, 270, 380)];
}
webview.backgroundColor=[UIColor clearColor];
webview.layer.borderWidth=4;
webview.layer.cornerRadius=4;
webview.layer.borderColor=[[UIColor whiteColor]CGColor];
[webview setTag:i];
webview.userInteractionEnabled=YES;
NSURL *url = [NSURL URLWithString:image];
[webview setImageWithURL:url placeholderImage:[UIImage
imageNamed:#"placeholder.png"]];
[scroll addSubview:webview];
UITapGestureRecognizer *tap1 =
[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handlefirstTap:)];
tap1.delegate=self;
tap1.numberOfTapsRequired=1;
[webview addGestureRecognizer:tap1];
[tap1 release];
[webview release];
}
pages.defersCurrentPageDisplay=YES;
scroll.delegate =self;
scroll.contentSize = CGSizeMake(290*[featuredarray count],300);
scroll.pagingEnabled = YES;
pages.numberOfPages = [featuredarray count]-1;
pages.currentPage =0;
}
-(void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView{
int gallerypage = scrollView.contentOffset.x /scrollView.frame.size.width;
CGRect frame = scroll.frame;
frame.origin.x = frame.size.width-15*gallerypage;
NSLog(#"frame.origin.x..%f",frame.origin.x);
frame.origin.y =0;
[scroll scrollRectToVisible:frame animated:YES];
}
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
int gallerypage = scrollView.contentOffset.x /scrollView.frame.size.width;
CGRect frame = scroll.frame;
frame.origin.x=gallerypage*290;
frame.origin.y =0;
[scroll scrollRectToVisible:frame animated:YES];
}
It is showing the images well when I use pagecontroller action
-(IBAction)pagecontrolaction:(id)sender {
int page = pages.currentPage;
CGRect frame = scroll.frame;
frame.origin.x=page*290;
frame.origin.y =0;
[scroll scrollRectToVisible:frame animated:YES];
}
but when I used touch event to imageview then it is not displaying the images smoothly. It gets stuck during scrolling.
These are the preview which shows the current image with the start of second image.
The two methods: scrollViewWillBeginDecelerating and scrollViewDidEndDecelerating contains two animation with different x position it is animated to:
frame.origin.x = frame.size.width-15*gallerypage;
and
frame.origin.x=gallerypage*290;
It is better if you could turn off either function: scrollViewWillBeginDecelerating or scrollViewDidEndDecelerating. Otherwise probably you should adjust the x position (must be in sync for both functions) it will be animated to.
if (i==0) {
webview=[[UIImageView alloc]initWithFrame:CGRectMake(i*320, 0, 270, 440)];
}
else {
webview=[[UIImageView alloc]initWithFrame:CGRectMake(i*320, 0, 270, 380)];
}
You need to change your implementation for loading the images from a remote resource, thats where the biggest bottle neck lies.
I don't want to go into details but try to replace this line of code
[webview setImageWithURL:url placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
With something more c.p.u friendly. Use a caching mechanism that employs multi-threading, what you want to do is move the heavy lifting (Downloading remote images) to another thread.
SDWebImage does a perfect job at this https://github.com/rs/SDWebImage
If you are a bit hard core, you can roll your own multi-threaded image downloader using
[NSURLConnection sendAsynchronousRequest:queue: completionHandler:]
And do the image loading in the block, Or even better dispatch_async(queu, block)
You should checkout SYPaginator (SYPaginator is a horizontal TableView with lazy loading and view recycling.)
Simple paging scroll view to make complicated tasks easier. We use
this in several of the Synthetic apps including Hipstamatic, D-Series,
and IncrediBooth.
If you need more perfomance you should give MSCachedAsyncViewDrawing a try:
Helper class that allows you to draw views (a)synchronously to a
UIImage with caching for great performance.
I think that happens because your images are larger in size (MB) , and you fit them into that CGRect without resizing them that specific size, also it matters how many pictures you have loaded inside the scrollview , if you load many pictures it will take a lot of memory and it will slide slowly.
I am not sure why are you setting the frame when you start scrolling, but if I were in your place I would do the logic as per below:
-(void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView{
// do nothing
}
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
int gallerypage = scrollView.contentOffset.x /scrollView.frame.size.width;
CGFloat xoffset = 290 * gallerypage;
[UIView animateWithDuration:0.5
animations:^{
scrollView.contentOffset = CGPointMake(xoffset, 0);
}
completion:^(BOOL finished){
NSLog(#"Done!");
}];
}
Hope it helps.
It happens when you are trying to show an image of larger size in that case you have to use cache to get the image there and try to load image from there.For better know how to use it..use SDwebimage thats the nice way to do it and it is fast.
You can read
Lazy Loading
2.paging enabled.
https://github.com/rs/SDWebImage
Need any further help.Happy to help you.
You should definitely resize your image in background prior to loading it into UIImageView. Also you should set UIImageView content mode to the none-resizing one. It gave me significantly scrolling improvement after other ways were already implemented (async cache and others).
Related
I have a gallery view that I use to, surprise, view images. :) I wanted it to be very similar to the standard iOS photo app with the same basic functionality: scrolling through images that are zoomable. To do this, I use a regular scroll view which I then populate with other scroll views that contain the images. The basic idea is something like this:
And, I do the populating the following way:
for (int i = 0; i < [imageGalleryArray count]; i++)
{
UIScrollView *imageContainer = [[UIScrollView alloc] initWithFrame:CGRectMake((windowSize.width * i), 0.0f, windowSize.width, windowSize.height)];
imageContainer.contentSize = CGSizeMake(windowSize.width, windowSize.height);
imageContainer.showsHorizontalScrollIndicator = NO;
imageContainer.showsVerticalScrollIndicator = NO;
imageContainer.bouncesZoom = YES;
imageContainer.delegate = self;
imageContainer.minimumZoomScale = 1.0f;
imageContainer.maximumZoomScale = 3.0f;
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, windowSize.width, windowSize.height)];
imageView.image = [[imageGalleryArray objectAtIndex:i] objectForKey:#"image"];
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.tag = IMAGEZOOMVIEW;
imageView.opaque = YES;
[imageContainer addSubview:imageView];
[galleryScrollView addSubview:imageContainer];
// Add it to array for proper removal later
[contentScrollViewSubviewList addObject:imageContainer];
}
Quite simple and it works fine. The only problem arises when I am zoomed into an image and want to scroll to another. The expected behavior in this case would be to set the zoom level of all images back to their original size. I do this in the scrollViewDidEndDecelerating delegate method like so:
if (scrollView.tag == GALLERYSCROLLVIEW)
{
for (int i = 0; i < [contentScrollViewSubviewList count]; i++)
{
[[contentScrollViewSubviewList objectAtIndex:i] setZoomScale:1.0f animated:YES];
}
}
Unfortunately, it does not work (or rather not as intended). I did some poking around and found out that the image does indeed animate back to its original size when it is the active slide. To elaborate: if I zoom into an image and scroll to another, the image stays zoomed in. When I scroll back to it, that is when it zooms back to its original size. I, however, need the image to scale back as soon as I move away from it ().
just like in the iOS image viewer).
Frankly, I do not understand the whole zoom mechanic enough to find out how to do this properly, and the UIScrollView documentation is of no help either. Could someone show me how to solve my problem?
I'm working in a project (iOS7 & ARC) in which, I want to display N number of images in the scroll view.These Images already stored into sandbox directory. My App has only landscape orientation I'm facing a problem that ScrollView is not smooth, it stuck after 2-3 times scroll
This is how I configure ScrollView
[self.containerScroll setAutoresizesSubviews:NO];
self.containerScroll.pagingEnabled = YES;
self.containerScroll.showsHorizontalScrollIndicator = NO;
self.containerScroll.showsVerticalScrollIndicator = NO;
self.containerScroll.scrollsToTop = NO;
self.containerScroll.maximumZoomScale = 5.0;
self.containerScroll.minimumZoomScale = 1.0;
self.containerScroll.delegate = self;
I'm maintaining only three Images in the scrollView at a time.
I'm loading Images in ScrollView in below method
-(void) loadScrollViewWithPage:(int) page{
if (page >= self.numberOfSlides)
return;
float image_width;
float image_height;
if(self.isFromListView){
if(IS_IPHONE5){
image_width = 568.0f;
image_height = 320.0f;
} else{
// iPhone retina-3.5 inch
image_width = 480.0f;
image_height = 320.0f;
}
}
else{
image_width = IMAGE_WIDTH;
image_height = IMAGE_HEIGHT;
}
CGFloat xPos = page * image_width;
UIImageView *imgView = [[UIImageView alloc] initWithFrame:CGRectMake(xPos, 0.0f, image_width, image_height)];
imgView.tag = page;
NSString *imgPath = [self.storageDirPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%d%#", page, Image_Extension_JPG]];
NSFileManager *fileManager = [NSFileManager defaultManager];
__block UIImage *img = nil;
if(![fileManager fileExistsAtPath:imgPath]){
[imgView setContentMode:UIViewContentModeCenter];
img = [UIImage imageNamed:#"icon-loader.png"];
[imgView setImage:img];
}
else{
[imgView setContentMode:UIViewContentModeScaleAspectFit];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
img = [[UIImage alloc] initWithCGImage:[[UIImage imageWithData:[NSData dataWithContentsOfFile:imgPath]] CGImage] scale:1.0 orientation:UIImageOrientationUp];
dispatch_async(dispatch_get_main_queue(), ^{
[imgView setImage:img];
});
});
}
[self.containerScroll addSubview:imgView];
img = nil;
fileManager = nil;
imgView = nil;
}
and this how my ScrollView Delegate methods goes...
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
self.containerScroll.scrollEnabled = YES;
float page = self.containerScroll.contentOffset.x/self.view.frame.size.width;
showingSlide = (UInt16) roundf(page);
if(scrollView == self.containerScroll){
// switch the indicator when more than 50% of the previous/next page is visible
CGFloat pageWidth = CGRectGetWidth(self.containerScroll.frame);
NSUInteger pageNo = floor((self.containerScroll.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
// load the visible page and the page on either side of it (to avoid flashes when the user starts scrolling)
[self loadScrollViewWithPage:pageNo - 1];
[self loadScrollViewWithPage:pageNo];
[self loadScrollViewWithPage:pageNo + 1];
// a possible optimization would be to unload the views+controllers which are no longer visible
if(scrollView == self.containerScroll)
{
[self.previewTableView reloadData];
[self.previewTableView setContentOffset:CGPointMake(0, (page*220)+64) animated:NO];
[self.previewTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:page inSection:0] atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
[self updateSlideNumber];
[self flashSlideNumber];
}
//unload unnecessary imageviews from scroll view
for (UIView* view in self.containerScroll.subviews) {
if ([view isKindOfClass:[UIImageView class]] && view.tag != page && view.tag != page-1 && view.tag != page+1) {
[view removeFromSuperview];
}
}
}
}
Now the problem is smoothness of scrollView. When I start scrolling it scrolls fine but after 2 or 3 (or after any random number) pages scroll, it stuck and after trying 2-3 times only it moves again and I have to swipe hard to scroll. Thanks in advance.
I think it's a problem of memory somewhere Try #autorelease pool in your code.
using scrollview is not a good approach for showing images, I will recommend you to either use tableview or collevtionview for the same.
Your app's memory will keep on increasing with every scroll because scollview doesn't reuse the memory, on the other hand tableview and collectionview reuses the memory.
As the most effective way to become better, scroll really slowly (one at a time) while you monitor the memory usage in your App. You'll be able to watch it go up as each new image is added to the view, especially if you haven't done any optimisation on the images.
The other thing is that while your code does look like is deallocc-ing the images, you still need to remember that it still has to try to reload the images as you scroll. You're creating your images on the main thread so you're never going to get the smoothness of a UITableView. While I realise that you're creating your image views on async threads, the act of adding and scrolling them is still being taken care of by the mainthread.
I would suggest a UITableView to solve your problem, or a UICollectionView. If you're set on using the scrollview, I would suggest using a crusher of some type to get the image size to as small as possible, while still keeping quality decent.
If you need help on the TableView implementation you should find plenty of information around SO. Probably a good option if you still want it to look like a scroll view is just to make all seperators, headers etc to nil, and then just use lazy loading for the images.
You make two the mistake. At first: never use imageNamed for non graphics content (example, use imageNamed for button background). And second: you try load a big images in real time. So you scroll view have lags therefore. If you load all images before you show the scroll view the amination end lagging. But you can get memory warnings. So, you need optimise it. P.S. Sorry for my english
I have set up a UIScrollView with which I want to display 12 images (only 8 fit on screen) laid out horizontally. In the following image you can see the problem I'm having (which makes my scroll view not scroll), my constraints and the UIScrollView which I have added on storyboard:
I have called the following method on -(void)viewDidLoad, where I "set up"my scrollview (itemList is my scroll view property and itemNames a array with the images'names):
- (void)setupHorizontalScrollView
{
self.itemList.delegate = self;
[self.itemList setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.itemList setBackgroundColor:[UIColor blackColor]];
[self.itemList setCanCancelContentTouches:NO];
self.itemList.indicatorStyle = UIScrollViewIndicatorStyleWhite;
self.itemList.clipsToBounds = NO;
self.itemList.scrollEnabled = YES;
self.itemList.pagingEnabled = NO;
NSInteger tot=0;
CGFloat cx = 0;
for (; ; tot++) {
if (tot==12) {
break;
}
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:[self.itemNames objectAtIndex:tot]]];
CGRect rect = imageView.frame;
rect.size.height = 40;
rect.size.width = 40;
rect.origin.x = cx;
rect.origin.y = 0;
imageView.frame = rect;
[self.itemList addSubview:imageView];
cx += imageView.frame.size.width;
}
[self.itemList setContentSize:CGSizeMake(cx, [self.itemList bounds].size.height)];
}
I have added the [self.itemList setTranslatesAutoresizingMaskIntoConstraints:NO]; because I saw this suggestion on other posts, but it doesn't work with or without it. The only way it works is if I uncheck use AutoLayout on the storyboard, but that moves the UIImageViewI use to look as a navigation bar to the bottom of the screen.
I don't know what to do anymore, any help is appreciated :)
Try to set your scrollView's Content size int "viewDidLayoutSubviews" method with keeping the autolayouts set.
-(void)viewDidLayoutSubviews
{
[self.itemList setContentSize:CGSizeMake(required_width, required_height)];
}
Two Solutions:
Create different constraints that can be satisfied simultaneously (you will have to edit). I think the problem is your bottom space and top space constraints are mutually exclusive. please remove one and try again. IF this is difficult for you, try adding another UIView to contain the UIScrollView to help manage your constraints, it might seem odd at first, but sometimes adding another view to contain your view actually makes it simpler at each level.
Turn off Autolayout, and change the autoresize masks of your UIImageView to be what you wish.
Insert: [scrollView setContentSize:CGSizeMake(x,y)]; in the following method:
-(void)viewWillAppear:(BOOL)animated
I'm using a double for loop to add UIButtons to a UIScrollView in a grid format. These UIButtons take time to load as they have subviews that are UIImageViews which get their UIImages by downloading data off the internet.
Right now, the subviews don't show until AFTER the method completely finishes executing. Correct me if I'm wrong, but I'm guessing xcode doesn't show added subviews until a method is done executing.
However, I do want to show each subview getting added one at a time, as a cool loading effect. How would I implement this?
Thanks!
You should use multiple threads to load your pictures so that your main thread does not become sluggish. I recently wrote something similar...Take a look at my code from my viewWillAppear method:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
self.myImages = [self.myModel retrieveAttachments]; //Suppose this takes a long time
for (UIImage *image in self.myImages)
{
dispatch_async(dispatch_get_main_queue(), ^{
[self addImageToScrollView:image animated:YES]; });
}
}
});
The addImageToScrollView method would be like so:
-(void) addImageToScrollView: (UIImage *) image animated: (BOOL) animated
{
//Create image view
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
imageView.image = image;
if(animated)
{
imageView.alpha = 0;
[self.myScrollView addSubview:imageView];
[UIView animateWithDuration:ADD_IMAGE_APPEARING_ANIMATION_SPEED animations:^{
imageView.alpha = 1;
}];
}
else
{
[self.myScrollView addSubview:imageView];
}
}
I'm having some problems in my usage of the TiledScrollView from Apple (3_Tiling/​Classes/​TiledScrollView.m) where sometimes the tiles don't show as I scroll and other times they do show. To clarify, the problem I'm seeing is analogous to having a tableview list of 100 rows and only 10 rows are being displayed at a time. As you scroll through the list, sometimes one or more rows are blank and stay blank because once they're displayed on the screen there is no reloading to make sure the content is there. However once these blank rows go off screen and say you scroll back to them, they show up with the content.
It seems to be completely random with no discernible patterns to it's behaviour. I know the delegate method ((UIView *)tiledScrollView:(TiledScrollView *)tiledScrollView tileForRow:(int)row column:(int)column resolution:(int)resolution) is executing thru NSLog's.
My Question is:
1. Have you encountered this phenomenon and how did you solve it? or
2. My debugging skill are very rudimentary. If I wanted to isolate the problem by seeing whether the tile or subviews exists, or the imageView was not able to fetch the image or if its a rendering problem... how would I go about debugging this?
Note- the delegate method shown below is a stripped down version of the above tiledScrollView delegate method where I removed the row and resolution portions of the code since there is no need for it if I'm just scrolling horizontally.
- (UIView *)tiledScrollView:(HorizontalTiledScrollView *)tiledScrollView column:(int)column {
NSLog(#"+++ %s +++", __PRETTY_FUNCTION__);
// re-use a tile rather than creating a new one, if possible
UIView *tile = [tiledScrollView dequeueReusableTile];
if (!tile) {
// the scroll view will handle setting the tile's frame, so we don't have to worry about it
if (tiledScrollView == self.timeHour) {
tile = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, TIMEHOUR_COLUMN_WIDTH, TIMEHOUR_COLUMN_HEIGHT)] autorelease];
} else if (tiledScrollView == self.timeMinute) {
tile = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, TIMEMINUTE_COLUMN_WIDTH, TIMEMINUTE_COLUMN_HEIGHT)] autorelease];
}
// Some of the tiles won't be completely filled, because they're on the right or bottom edge.
// By default, the image would be stretched to fill the frame of the image view, but we don't
// want this. Setting the content mode to "top left" ensures that the images around the edge are
// positioned properly in their tiles.
[tile setContentMode:UIViewContentModeTopLeft];
}
for(UIView *subview in [tile subviews]) {
if (subview.tag != 3) {
[subview removeFromSuperview]; //remove all previous subviews in tile except tile annotation if present
}
}
UIImageView *imgView = [[UIImageView alloc] init];
UILabel *digitLabel;
// Add blank UIImageView as filler or UIImageView with PNG or UILabel if no PNG sized correctly and offsetted from tile's origin as subviews in the tile
if (tiledScrollView == self.timeHour) {
if (column < 1) {
imgView.frame = CGRectZero;
[tile addSubview:imgView];
[tile bringSubviewToFront:imgView];
} else {
int digitH = ((column - 1) % 12 + 1);
imgView.frame = CGRectMake(9, 0, 17, 21);
[imgView setContentMode:UIViewContentModeScaleToFill];
if ((imgView.image = [UIImage imageNamed:[NSString stringWithFormat:#"TimeHour_%02d.png", digitH]])) {
[tile addSubview:imgView];
[tile bringSubviewToFront:imgView];
} else {
// NSLog(#"No TimeHour_%02d.png", digitH);
digitLabel = [self makeDigitLabel:digitH frame:imgView.frame fontSize:14.0];
[tile addSubview:digitLabel];
[tile bringSubviewToFront:digitLabel];
}
}
} else if (tiledScrollView == self.timeMinute) {
// if (column % 2) {
// tile.backgroundColor = [UIColor redColor];
// } else {
// tile.backgroundColor = [UIColor blueColor];
// }
if (column < 1) {
imgView.frame = CGRectZero;
[tile addSubview:imgView];
[tile bringSubviewToFront:imgView];
} else {
int digitM = ((column - 1) % 60);
imgView.frame = CGRectMake(9, 0, 16, 15);
[imgView setContentMode:UIViewContentModeScaleToFill];
if ((imgView.image = [UIImage imageNamed:[NSString stringWithFormat:#"TimeMinute_%02d.png", digitM]])) {
[tile addSubview:imgView];
[tile bringSubviewToFront:imgView];
} else {
NSLog(#"No TimeMinute_%02d.png", digitM);
digitLabel = [self makeDigitLabel:digitM frame:imgView.frame fontSize:12.0];
[tile addSubview:digitLabel];
[tile bringSubviewToFront:digitLabel];
}
}
}
}
[imgView release];
NSLog(#"Tile: %d",[tile.subviews count]);
return tile;
}
Hope it's clear.
Thanks for helping,
Hiren
I finally solved this!
The issue wasn't in the code posted above. It was in the layoutSubviews method of Apple's TiledScrollView. There is a line of code that calculates the maximum column shown below (max row has a similar calculation)
int lastNeededCol = MIN(maxCol, floorf(CGRectGetMaxX(visibleBounds) / scaledTileWidth));
In my case, this formula calculates one extra column than is needed and this column tile ends up sitting off screen. This is fine when you're not scrolling around and setting animated to NO. But when you do scroll around and/or set animated to YES in scrollview setContentOffset method call, you will sometimes end up with a missing tile because of the delay due to animation or if you scrolled really slowly. The animation or moving really slowly causes the scrollview to detect there is a movement and thus calls the layoutSubviews method where a line of code checks to see which tiles are visible and drops non-visible tiles. Now if you did this just right, then the extra tile created earlier gets dropped because its still off-screen and is never created again until the tile has moved far enough off-screen triggering a reload of that tile.
The fix I did was to change the above line to:
int lastNeededCol = MIN(maxCol, floorf((CGRectGetMaxX(visibleBounds)-0.1) / scaledTileWidth));
This new formula calculates the right number of columns of tiles needed to be displayed on screen thereby eliminating the whole thing of dropping extra tiles sitting off-screen.
I create an example code on github that helps demonstrates this.
https://github.com/hmistry/TiledScrollViewDebug