iOS dynamic object creation and storage - ios

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.

Related

Why can't I access to nested UIView from viewWithTag method using StoryBoard?

I created some UIImageViews inside a nested UIView by Storyboard and I created a different TAG for each UIImageView. This is how the tree of the ViewController looks according to Storyboard:
ViewController
View
ViewNested
UIImageView1
UIImageView1
UIImageView1
UIImageView1
I have to change programmatically these ImageViews so, to get the images I use the method viewWithTag but it doesn't work because it returns NIL.
This happens even if I add to my class the ViewNested IBOutlet and getting the views using the following code:
// View is the top View with Tag:40
UIView * view = [self.view viewWithTag:40]; //This works
// The nestedView with Tag:44
UIView * viewNested = [view viewWithTag:44]; //DOESN'T work it returns NIL even if the TAG is exact
Then if I try to access to the imageView using the same method of course, it returns NIL. I don't know why, I also tried to use this code to view all the recursive nested view but it seems that they don't exist even if they are present in the storyboard.
- (void)showAllSubView:(UIView *)view
{ int i = 0;
for (UIView * subView in [view subviews]) {
NSLog(#"%#, tag:%ld", subView, (long)subView.tag) ;
[self showAllSubView:subView] ;
i++;
}
NSLog(#"Numb of Views %d", i) ;
}
The TAGS are 40 for the root View, 44 for the nested and for the images are 1,2,3,4. So the TAGS are all different.
Any help will be appreciate :). Thanks in advance
UIImageView *imageView=(UIImageView *)[self.view viewWithTag:yourTag];
[imageView setImage:[UIImage imageNamed:#"yourImageName"]];
replace yourTag with UIImageView tag;
replace yourImageName with some Image name
and if you want change only images of ImageViews - you don't need
UIView * view = [self.view viewWithTag:40]; //This works
// The nestedView with Tag:44
UIView * viewNested = [view viewWithTag:44];
Also you can change all images:
for (int i=0; i<18; i++)
{
UIImageView *imageView=(UIImageView *)[self.view viewWithTag:i];
NSString *imageName = [NSString stringWithFormat:#"imageName_%d", i];
[imageView setImage:[UIImage imageNamed:imageName]];
}

Destroy UIImageView objects after they move out of bounds

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.

UITableViewCell contentView custom disclosure image frame issue

Disclaimer: I've been working too late. But, I'm determined to get through this one tonight.
I have an app where I support different color themes. The dark cell backgrounds have been problematic.
I've been poking around trying to find a formidable way to draw the accessory disclosure icon in uitableviewcells with black backgrounds.
I decided to try overriding setAccessoryType to inherit the functionality for my 50+ views:
-(void) addWhiteDisclosureImage {
UIImageView *disclosureView = (UIImageView*) [self.contentView viewWithTag:kDisclosureReplacementImageTag];
if(!disclosureView) {
[super setAccessoryType:UITableViewCellAccessoryNone];
disclosureView = [[UIImageView alloc] initWithImage:self.whiteDisclosureImage];
disclosureView.tag = kDisclosureReplacementImageTag;
disclosureView.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin;
DebugLog(#"%f, %f", self.frame.size.width, self.frame.size.height);
[self.contentView addSubview:disclosureView];
[self.contentView bringSubviewToFront:disclosureView];
[disclosureView release];
}
}
- (void)setAccessoryType:(UITableViewCellAccessoryType)accessoryType {
if(accessoryType == UITableViewCellAccessoryDisclosureIndicator) {
if ([self.viewController isKindOfClass:[ViewControllerBase class]]) {
ViewControllerBase *view = (ViewControllerBase*) self.viewController;
if(view.colorTheme && view.colorTheme.controlBackgroundColor) {
if([ViewColors colorAverage:view.colorTheme.controlBackgroundColor] < 0.2) { //substitute white disclosure indicator
[self addWhiteDisclosureImage];
return;
} else { //not dark enough
[self removeWhiteDisclosureImage];
[super setAccessoryType:accessoryType];
return;
}
} else { //no colorTheme.backgroundColor
[self removeWhiteDisclosureImage];
[super setAccessoryType:accessoryType];
return;
}
} else { //viewController is not type ViewControllerBase
[self removeWhiteDisclosureImage];
[super setAccessoryType:accessoryType];
return;
}
}
UIView *disclosureView = [self.contentView viewWithTag:kDisclosureReplacementImageTag];
if(disclosureView)
[disclosureView removeFromSuperview];
[super setAccessoryType:accessoryType];
}
This override is typically called in cellForRowAtIndexPath.
It seemed like a good option until I drill down and come back. For some cells, the cell frame will be a great deal larger than the first time through. This consistently happens to the same cell in a list of 6 that I've been testing against. There's clearly something unique about this cell: it's frame.size.
Here is the size of the cell that I log for the first tableview load (in some cases every load/reload):
320.000000, 44.000000
This is the difference in what I get for some (not all) of the cells after call to reloadData:
759.000000, 44.000000
Does anyone know why this might happen?
Update: the suspect cell's custom accessory disclosure view almost acts like it's autoresizing flag is set to none. I confirmed this by setting all to none. I say almost because I see it line up where it should be after reloadData. A split second later it moves clear over to the left (where they all end up when I opt for no autoresizing).
Don't mess around with subviews and calculating frames.
Just replace the accessoryView with the new imageView. Let iOS do the work.

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

TiledScrollView doesn't display some tiles when scrolled to that area

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

Resources