Destroy UIImageView objects after they move out of bounds - ios

In my application I have some images falling from above inside a UIView. The UIImageView objects are created dynamically and made to move downwards. My question is, do the objects destroy themselves after they move off below the screen area? Or should I do it manually to improve performance?

Once there are no more strong references to an object, it will be deallocated. The two references you probably need clear will be the variable pointer, and the superview's reference to it. So you'd need to do something like:
[imageView removeFromSuperView];
imageView = nil;
It's possible there are more as you did not provide any code (if for instance you have a pointer to the object in an array, you'd need to remove that too).

Removing from superview is all that's needed if you don't have any other pointers to the image view (in ARC). The most performant pattern is to keep a pool of pointers to offscreen image views. In pseudo code:
// this assumes the number onscreen is relatively small, in the tens or low hundreds
// this works better when the number on screen is relatively constant (low variance)
// say the animation looks something like this:
- (void)makeAnImageFall:(UIImage *)image {
CGRect startFrame = // probably some rect above the superview's bounds
UIImageView *imageView = [self addImageViewWithImage:image frame:frame]; // see below
[UIView animateWithDuration:1.0 animations:^{
imageView.frame = // probably some rect below the superview's bounds
} completion:^(BOOL finished) {
[self removeImageView:imageView]; // see below
}];
}
Then these methods do everything to handle the pool:
- (UIImageView *)addImageViewWithImage:(UIImage *)image frame:(CGRect)frame {
UIImageView *imageView;
// assume you've declared and initialized imageViewPool as an NSMutableArray
// or do that here:
if (!self.imageViewPool) self.imageViewPool = [NSMutableArray array];
if (self.imageViewPool.count) {
imageView = [self.imageViewPool lastObject];
[self.imageViewPool removeLastObject];
} else {
imageView = [[UIImageView alloc] init];
}
imageView.image = image;
imageView.frame = frame;
[self.view addSubview:imageView];
return imageView;
}
- (void)removeImageView:(UIImageView *)imageView {
imageView.image = nil;
[self.imageViewPool addObject:imageView];
[imageView removeFromSuperview];
}
The nice idea is that we avoid the relatively expensive churn of create-destroy of many image views. Instead, we create them (lazily) when they're first needed. Once we hit the high water mark of image views, we don't ever allocate another.

Related

create a UILabel when view appears and destroy it when view disappears

For the tutorial for my app, I am trying to create a UILabel that drifts across the displayed screen when a view appears and then is destroyed so that if the user comes back to that view during the tutorial, the UILabel will be created anew and again drift across the page. Here's a sample of one of the custom view controllers I am displaying with my UIPageViewController:
//this is just a custom UILabel with some padding
#property (nonatomic) PaddedLabel *directionsLabel;
//I have tried setting UILabel to nil or removing it from view
-(void)viewWillDisappear:(BOOL)animated
{
NSLog(#"view is disappearing");
//this does not remove the label
self.directionsLabel = nil;
//nor does this
[self.directionsLabel removeFromSuperview];
}
- (void)viewDidAppear:(BOOL)animated
{
[self messageTutorial];
}
- (void)messageTutorial
{
CGFloat width = [[UIScreen mainScreen] bounds].size.width;
CGFloat height = [[UIScreen mainScreen] bounds].size.height;
PaddedLabel *directionsLabel = [[PaddedLabel alloc] initWithFrame:CGRectMake(_width/2, _height/2, 100, 100)];
directionsLabel.text = #"test";
CGRect f = directionsLabel.frame;
f.size = size;
directionsLabel.frame = f;
directionsLabel.center = self.view.center;
f = directionsLabel.frame;
f = directionsLabel.frame;
f.origin.y = .1*height;
directionsLabel.frame = f;
[self.view addSubview:directionsLabel];
[UIView animateWithDuration:TUTORIAL_DISAPPEAR_TIME animations:^{
directionsLabel.alpha = .5;
CGRect f = directionsLabel.frame;
f.origin.y = height - f.size.height*1.4;
directionsLabel.frame = f;
NSLog(#"animating");
} completion:^(BOOL finished) {
[directionsLabel removeFromSuperview];
//this also doesn't actually remove the label
}];
}
The problem is that if the user pages back to see this view she now sees a new label and the old one, so that if you page back and forth back and forth you end up with many many labels all saying the same thing, in different stages of progressing across the screen.
How can I remove the UILabel when the view disappears and add/create a new one when the view appears/reappears?
Thank you.
The code in your viewWillDisappear method is backwards. You need:
- (void)viewWillDisappear:(BOOL)animated {
NSLog(#"view is disappearing");
[self.directionsLabel removeFromSuperview];
self.directionsLabel = nil;
}
As you had it, setting self.directionsLabel to nil before trying to remove it results in a no-op.
Also, be sure to set self.directionsLabel when you create the label.
Instead of setting your label to nil and effectively destroying the label object (assuming automatic reference counting is on) rather use the following method to hide and show the label as and when your need it.
[self.directionsLabel setHidden:YES]; // hides it
[self.directionsLabel setHidden:NO]; // shows it
You've got the right idea setting objects you're not using to nil and removing them from the super view but it's over kill. A UILabel object uses a negligible amount of memory and you're better off creating the object once and then changing it's properties as you need to.
You don't seem to be setting self.directionsLabel to anything when you create the directionsLabel inside the messageTutorial method. It is a local instance of the label there. You should set it in the method somewhere.
Afterwards, removing it from the superview in viewWillDisappear will work (tested to verify).

iOS dynamic object creation and storage

What I have is a UIButton. What I want to do is, every time the button is clicked, a new UIImageView is created (I will set the size/image etc... in a method) and the UIImageView falls to the bottom of the screen. How do I do this? I've tried creating the object and storing it in an NSMutableArray and doing a for loop on the array but that doesn't seem to work. (example of what I'm doing)
-(IBAction) button {
[self createUiImage];
}
-(void) createUiImage {
UIIMageView *iv = [[UIImageView alloc] initWithFrame:CGRectMake(50, 50, 15, 3)]
iv.image = [UIImage imageNamed:#"image.png"];
iv.hidden = NO;
[self.view addSubview:iv];
[array1 addObject:iv];
}
-(void) dropImageDown {
for (UIImageView *a in array1) {
a.center = CGPointMake(a.center.x, a.center.y + 10);
if (a.center.y > 500) {
[array1 removeAllObjects];
[a removeFromSuperview];
}
}
}
and this dropImageDown method is being controlled by an NSTimer.
forgot to add this: The problem is that the shape isn't falling to the bottom, it appears and doesn't move!
I've also tried a for (int i = 0; i < array size; i ++) but thats not working either
Appreciate the help, thanks
Don't try to use a timer and move the view in steps yourself. It will give jerky animation that puts a large burden on the CPU.
Use UIView animation instead.
Something like this:
-(void) animateImagesDown {
for (UIImageView *a in array1)
{
[UIView animateWithDuration: 1.0
animations: ^
{
a.center = CGPointMake(a.center.x, 500);
}
completion: ^(BOOL finished)
{
[a removeFromSuperview];
}
];
}
}
That code would animate all the view to a center position of 500, then remove them. (They would all be animated at the same time.)
UIView animations use ease-in, ease-out animation by default. There are other variations on the animateWithDuration UIView animation methods that let you control the animation timing. (look at the docs on the methodanimateWithDuration:delay:options:animations:completion:(Specifically the options parameter.)
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithObjects/WorkingwithObjects.html use this link which can be help to understand the concept
Found the reason I was having issues... I was not initializing the NSMutableArray therefore it wasn't being recognized.

Scroll view is not smooth

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

iOS adding subviews to a scroll view

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];
}
}

Demand loading a UIScrollView

I would like to implement an app using a UIScrollView with paging, similar to the apple weather app.
But I am a little concerned about performance. The example implementation I have been using loads all of the views then the application launches. After a certain point, once this prove slow?
I wonder how Apple's camera roll is dealing with this, where a user may have 100+ photos that can be scrolled through. Should I try to figure out a way to build the view only when it is needed? Or maybe there is a way to replicate the dequeue reusable cell technique from a UITableView, only for horizontal view loading, since each view will have the same layout.
By far the most efficient solution (and this is used in many photo-browsing apps such as Facebook, and probably the native Photos app too) is going to be to load the content on-demand, just as UITableView does. Apple's StreetScroller sample project should get you on the right track.
A very efficient solution, is to make sure to reuse any views whenever possible. If you are going to be simply displaying images, you could use a subclass of UIScrollView, and layout these reusable views within layoutSubviews. Here you could detect what views are visible and not visible and create the subviews as needed.
An example dequeuing function may look like:
- (UIImageView *)dequeueReusableTileWithFrame:(CGRect) frame andImage:(UIImage *) image
{
UIImageView *tile = [reusableTiles anyObject];
if (tile) {
[reusableTiles removeObject:tile];
tile.frame = frame;
}
else {
tile = [[UIImageView alloc] initWithFrame:frame];
}
tile.image = image;
return tile;
}
Where reusableTiles is just an iVar of NSMutableSet type. You could then use this to load fetch any currently offscreen image views and quickly and easily bring them back into view.
Your layoutSubviews may look something like:
- (void)layoutSubviews {
[super layoutSubviews];
CGRect visibleBounds = [self bounds];
CGPoint contentArea = [self contentOffset];
//recycle all tiles that are not visible
for (GSVLineTileView *tile in [self subviews]) {
if (! CGRectIntersectsRect([tile frame], visibleBounds)) {
[reusableTiles addObject:tile];
[tile removeFromSuperview];
}
}
int col = firstVisibleColumn = floorf(CGRectGetMinX(visibleBounds)/tileSize.width);
lastVisibleColumn = floorf(CGRectGetMaxX(visibleBounds)/tileSize.width) ;
int row = firstVisibleRow = floorf(CGRectGetMinY(visibleBounds)/tileSize.height);
lastVisibleRow = floorf(CGRectGetMaxY(visibleBounds)/tileSize.height);
while(row <= lastVisibleRow)
{
col = firstVisibleColumn;
while (col <= lastVisibleColumn)
{
if(row < firstDisplayedRow || row > lastDisplayedRow || col < firstDisplayedColumn || col >lastDisplayedColumn)
{
UImageView* tile = [self dequeueReusableTileWithFrame:CGRectMake(tileSize.width*col, tileSize.height*row, tileSize.width, tileSize.height) andImage:YourImage];
[self addSubview:tile];
}
++col;
}
++row;
}
firstDisplayedColumn = firstVisibleColumn;
lastDisplayedColumn = lastVisibleColumn;
firstDisplayedRow = firstVisibleRow;
lastDisplayedRow = lastVisibleRow;
}
I used something similar to this to tile in areas of a line when I was working with an exceptionally large area of a scroll view and it seemed to work quite well. Sorry for any typos that I may have created when updating this for an image view instead of my custom tileView class.

Resources