Multiple NSTimer animation views - ios

- (void)createCar
{
_car = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 40, 10)];
[_car setBackgroundColor:[UIColor redColor]];
[self addSubview:_car];
_myTimer = [NSTimer scheduledTimerWithTimeInterval:normalSpeedValue target:self selector:#selector(moveCar) userInfo:nil repeats:YES];
}
- (void)moveCar
{
static int move = 0;
move = move+1;
[_car setFrame:(CGRectMake(move, 0, 40, 10))];
}
This is how I create a view and animate it moving from left to right.
If I call the method "createCar" again, it will just create a new view but I won't animate. Why is that?
I want to be able to create more views and animate (moveCar).

The reason that additional calls to createCar creates motionless, but still visible cars, is because the callback on the timer, moveCar, only has reference to the most recently created car stored in the _car ivar.
The past created cars are still visible because the view they were added to still holds reference to them and consequently continues to draw them.
You can fix this by creating an NSMutableArray for your cars, adding them to it in createCar, and then looping over the array moving each car in the moveCar method.
Sample Code:
// ...
NSMutableArray<UIView *> *_cars; // Be sure to init this somewhere
// ...
// ...
timer = NSTimer.schedule ... // Schedule time in viewDidLoad, or somwhere
// ...
- (void)createCar
{
UIView *_car = [[UIView alloc] initWithFrame: CGRectMake(0, 0, 100, 100)];
[_car setBackgroundColor: [UIColor redColor]];
[self.view addSubview: _car];
[_cars addObject:_car];
}
- (void)moveCars
{
// go through each car
[_cars enumerateObjectsUsingBlock:^(UIView *car, NSUInteger i, BOOL *stop) {
// and set its frame.x + 1 relative to its old frame
[car setFrame: CGRectMake(car.frame.origin.x + 1, 0, 100, 100)];
}];
}
This is one simple way of doing it. But if you want flexibility like different speeds for different cars, it'll take a little reworking but not much.
Hope this helps!

Every time your move became 0 when method get called. declared it as instance variable and set it's initial value 0 (in your case) in createCar method. I think this you want. hope this will help :)

Related

How do I keep adding UIButton and set them with different action programmatically?

Assuming I have a container that stores a list of items. By adding these items, I have to add a single UIView for each. I want to make a delete button that allows the user to delete the item that they don't want. How can I keep adding these buttons and separate them with different actions? Like this button is for deleting item A, and that button is for deleting item B?
P.S. This situation is not allow to use tableView, and I've already handled the view stacking part.
If you need me to show any of the code, please feel free to ask.
Updated:
The code of adding the Item:
-(void)appendAttachmentRow:(AttachmentItem *)attachment
{
AttachmentRowView * attachmentRowView = [[AttachmentRowView alloc]init];
screenWidth = CGRectGetWidth(self.view.bounds);
screenHeight = CGRectGetHeight(self.view.bounds);
// Set up the view in a single attachment row
// Attachment row container
CGRect attachmentRowFrame = CGRectMake(0, yLastLocation, screenWidth, 50);
UIView *attachmentRow = [[UIView alloc]initWithFrame:attachmentRowFrame];
// Attachment name label
CGRect attachmentNameLabelFrame = CGRectMake(70, 20, screenWidth / 3, 15);
UILabel *attachmentNameLabel = [[UILabel alloc]initWithFrame:attachmentNameLabelFrame];
// Attachment thumbnail image
CGRect attachmentImageThumbnailFrame = CGRectMake(10, 0, 50, 50);
UIImageView *attachmentImageThumbnail = [[UIImageView alloc]initWithFrame:attachmentImageThumbnailFrame];
CGRect attachmentRemoveFrame = CGRectMake(screenWidth - 40, 10, 30, 30);
attachment.attachmentRemove = [[UIButton alloc]initWithFrame:attachmentRemoveFrame];
[attachment.attachmentRemove setImage:[UIImage imageNamed:#"removeAttachmentButton"] forState:UIControlStateNormal];
[attachment.attachmentRemove addTarget:self action:#selector(removeAttachment:) forControlEvents:UIControlStateNormal];
attachmentImageThumbnail.image = attachment.attachmentImage;
attachmentNameLabel.text = attachment.attachmentName;
attachmentRow.layer.borderColor = [UIColor lightGrayColor].CGColor;
attachmentRow.layer.borderWidth = 1.0f;
[attachmentRow addSubview: attachmentImageThumbnail];
[attachmentRow addSubview: attachmentNameLabel];
[attachmentRow addSubview: attachment.attachmentRemove];
[[self attachmentCellCellIt] addSubview: attachmentRow];
[attachmentArray addObject:attachment];
yLastLocation += 50;
[[self attachmentCellCellIt]setFrame:CGRectMake(0, 337, screenWidth, yLastLocation)];
You need to give tag to button after creating UIView for an attachment.
Keep the method name same and try to work with the tag value.
For ex :
button.tag = 1000; // while creating it.
In method you passed UIButton as parameter
Inside method body
NSInteger tag = button.tag
[array removeObjectAtIndex:tag];
Hope i understand your situation,
You want to set selector dynamically. Say you have following selector declarations.
-(void)onPressA:(id)sender{ ... }
-(void)onPressB:(id)sender{ ... }
-(void)onPressC:(id)sender{ ... }
-(void)onPressD:(id)sender{ ... }
Now need to take an NSArray or other storage to store them. Let save them in array. To do that, you need to convert them into NSString as following
NSArray *selectorArr = #[NSStringFromSelector(#selector(onPressA:)),
NSStringFromSelector(#selector(onPressB:)),
NSStringFromSelector(#selector(onPressC:)),
NSStringFromSelector(#selector(onPressD:))];
Now you can back & forth from NSString to SEL & SEL to NSString as following.
SEL selector = NSSelectorFromString(selectorArray[/*suitable index*/]);
Now you can easily add and remove target by using
[btn addTarget:/*target*/ action:/*selector*/ forControlEvents:/*UIControlEvents*/];
[btn removeTarget:/*target*/ action:/*selector*/ forControlEvents:/*UIControlEvents*/];
addTarget:action:forControlEvents: appledoc
removeTarget:action:forControlEvents: appledoc
You need to keep track which SEL was previously assigned so that you can remove it.
Happy coding :)
why you want to set different actions? you can do this by set tag property of button. on action you can check tag value and can do different tasks i guess.
You can get the button object from which the function is called in the following method declaration in "sender" object.
-(IBAction)buttonClicked:(id)sender
from this you can get the tag of the UIButton from which the function is called and perform the desired action.

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.

Programatically generated UILabel origin point incorrectly to (0.0) set on first load

I am trying to programatically generate two UILabels in my application for each UIImageView on my storyboard. The code runs and works correctly, however, on first load the two UILabels form in the (0.0) coordinate of the main view, as opposed to the UIImageView frame origin.x,origin.y. I can't understand why this is happening.
If I then click on a different tab and return to the page, the labels generate in the correct location.
Why is this? How can I get it to initially generate the labels in the correct location?
-(void) viewWillAppear:(BOOL)animated
{
//removed unneccessary code above...
int i = 0;
for (UIImageView *plantScreen in self.view.subviews)
{
if ([plantScreen isMemberOfClass:[Plant class]])
{
#try
{
//the label which will hold the name
UILabel *plantName = [[UILabel alloc] initWithFrame:CGRectMake((plantScreen.frame.origin.x), (plantScreen.frame.origin.y+ plantScreen.frame.size.height), 160, 30.0)];
plantName.numberOfLines = 1;
plantName.minimumScaleFactor = .5;
plantName.adjustsFontSizeToFitWidth = YES;
[plantName setTextAlignment:NSTextAlignmentCenter];
[plantName setBackgroundColor:[UIColor clearColor]];
[self.view addSubview:plantName];
plantName.hidden = false;
[self.view bringSubviewToFront:plantName];
//create the label which will hold the quantity
UILabel *quantity = [[UILabel alloc] initWithFrame:CGRectMake((plantScreen.frame.origin.x), (plantScreen.frame.origin.y+ plantScreen.frame.size.height + 20), 160, 30.0)];
[quantity setTextAlignment:NSTextAlignmentCenter];
[quantity setBackgroundColor:[UIColor clearColor]];
quantity.text = [NSString stringWithFormat:#"%d",plant.quantity];
[self.view addSubview:quantity];
quantity.hidden = false;
[self.view bringSubviewToFront:quantity];
i++;
}
#catch (NSException *exception)
{
NSLog(#"An exception occured: %#", [exception reason]);
}
#finally
{
}
}
}
}
Frame of the UIImageView depends on the image being drawn and its contentMode property. You can try setting the contentMode to UIViewContentModeScaleAspectFill to see if it forces to keep its assigned frame.
First things first, you're missing a call to [super viewWillAppear:animated]. You need to give the superclass (including the UIViewController base class) a chance to "do its magic".
Never forget about giving the parent class a chance to do its magic, unless you know really really well what you're doing.
Second, UI creation should be done in -loadView, not in viewWillAppear:.
Try these two things first.
Alright. Now I'm curious about how you moved things to -loadView. Did you add [super loadView];?
In fact, now that I think about it, moving to -loadView is wrong in this case; you obviously instantiate some views through a nib. UIViewController's implementation of -loadView typically just loads the nib file. Once that's done, UIViewController's -loadView calls -viewDidLoad.
So when you're not creating all UI programmatically but are instead allowing UIViewController to load it from nib, you actually probably want to move code into -viewDidLoad. (See template generated by Xcode when you tell it to create a new UIViewController subclass.)
Moving on, let's consider what the frame depends on. It depends on some view class you called Plant.
Please don't call it that way; it's confusing. Call it PlantView, so a casual reader of your code is aware of what the class is supposed to do. Similarly, you might want to call the variables plantView instead of plantScreen, and plantNameLabel instead of plantName. plantScreen implies a variable containing UIScreen, and plantName implies an NSString more than it implies a UILabel. Same applies to quantity; call this variable quantityLabel.
Next, let's consider what the variables are depending on -- their origin's x and y do not change based on the counter, variable i. Perhaps you meant to write:
UILabel *plantName = [[UILabel alloc] initWithFrame:CGRectMake((plantScreen.frame.origin.x), (plantScreen.frame.origin.y+ plantScreen.frame.size.height * i), 160, 30.0)];
and later on:
UILabel *quantity = [[UILabel alloc] initWithFrame:CGRectMake((plantScreen.frame.origin.x), (plantScreen.frame.origin.y+ plantScreen.frame.size.height*i + 20), 160, 30.0)];
Next, avoid exceptions and exception handling. Ensure that the exception does not occur via other forms of checking; Apple highly recommends you fix exceptions while writing the application and not handle them when they run:
Important: You should reserve the use of exceptions for programming or
unexpected runtime errors such as out-of-bounds collection access,
attempts to mutate immutable objects, sending an invalid message, and
losing the connection to the window server. You usually take care of
these sorts of errors with exceptions when an application is being
created rather than at runtime.
Next, a small stylistic remark (not very important): you're mixing calling setters via properties and calling setters directly. Nothing wrong (they end up doing exactly the same), but stylistically not very nice.
Next, unless you're using ARC (automatic reference counting), don't forget to release the views once they're added as subviews.
Next, plantScreen (which I named plantView below) can have type set to Plant (which I named PlantView below) when declared inside the loop.
Last but highly important and extremely easy to miss: you call the function isMemberOfClass: instead of isKindOfClass:.
Reworked version of your code (untested):
-(void)viewDidLoad
{
[super viewDidLoad];
int i = 0;
for (PlantView *plantView in self.view.subviews)
{
if ([plantView isKindOfClass:[PlantView class]])
{
//the label which will hold the name
CGRect plantNameLabelFrame = CGRectMake((plantScreenView.frame.origin.x),
(plantScreenView.frame.origin.y + plantScreen.frame.size.height*i),
160,
30.0);
UILabel *plantNameLabel = [[UILabel alloc] initWithFrame: plantNameLabelFrame];
plantNameLabel.numberOfLines = 1;
plantNameLabel.minimumScaleFactor = .5;
plantNameLabel.adjustsFontSizeToFitWidth = YES;
plantNameLabel.textAlignment = NSTextAlignmentCenter;
plantNameLabel.backgroundColor:[UIColor clearColor];
[self.view addSubview:plantNameLabel];
[plantNameLabel release];
//create the label which will hold the quantity
CGRect quantityLabelFrame = CGRectMake((plantScreenView.frame.origin.x),
(plantScreenView.frame.origin.y + plantScreen.frame.size.height*i + 20),
160,
30.0);
UILabel *quantityLabel = [[UILabel alloc] initWithFrame: quantityLabelFrame];
quantityLabel.textAlignment = NSTextAlignmentCenter;
quantityLabel.backgroundColor = [UIColor clearColor];
quantityLabel.text = [NSString stringWithFormat:#"%d", plant.quantity]; // NB: what's "plant"?
[self.view addSubview:quantityLabel];
i++;
}
}
}
I've also removed .hidden = false (which should actually read .hidden = NO; this is Objective-C, and not C++), and bringSubviewToFront: (it's already in front, having just been added by addSubview:).

How to update UILabel programmatically in iOS

I am facing a problem with updating my labels.. it doesn't remove the old values so new values go on top of old ones.. any help with this will be appreciated..
timer = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:#selector(updateLabels)
userInfo:nil
repeats:YES];
-(void) updateLabels{
for (GraphView *graph in arr){
// retrieve the graph values
valLabel = [[UILabel alloc] initWithFrame:CGRectMake(i * 200, 0, 90, 100)];
valLabel.textColor = [UIColor whiteColor];
valLabel.backgroundColor = [UIColor clearColor];
valLabel.text = [NSString stringWithFormat:#"Value: %f", x];
i++;
}
}
If you set the text of your label you do not need to call setNeedsDisplay or clearContext etc.
In your code, I do not know what are your variables i and x?
The main problem is that you are creating and adding new labels on your view. When you call updateLabels method, may cause a Memory leak. Simply you have n times labels overlapped.
You need to remove the labels before you create and add new labels or you can reuse which you already have. To reuse your labels you need to save them to an array and update texts.
If you want to create new labels then you can do like this unless you have other labels in your view
-(void) updateLabels{
// remove all labels in your view
for (UIView *labelView in self.view.subviews) {
if ([labelView isKindOfClass:[UILabel class]]) {
[labelView removeFromSuperview];
}
for (GraphView *graph in arr){
// retrieve the graph values
valLabel = [[UILabel alloc] initWithFrame:CGRectMake(i * 200, 0, 90, 100)];
valLabel.textColor = [UIColor whiteColor];
valLabel.backgroundColor = [UIColor clearColor];
valLabel.text = [NSString stringWithFormat:#"Value: %f", x];
i++;
}
}
When you create new labels like this you need to add them to your view as subview
[self.view addSubview: valLabel];
if you have other labels in your view then you can save them in an array and remove just them
Your updateLabels method is actually creating new UILabel controls each time so they will simply appear "on top of" older ones. I'm guessing this is not what you want, although it's not perfectly clear so apologies if I've misunderstood what you're trying to do.
If I'm correct about that, create your UILabel controls just once maybe in your viewDidLoad or similar. Then just set their .text properties when your timer fires.
You need to call setNeedsDisplay so that the app knows it has changed and redraw it.
- (void)setNeedsDisplay
Set clearsContextBeforeDrawing property of your label to YES
you can set this from nib as well as code.
label.clearsContextBeforeDrawing = YES;

Update UIview with new objects

I have a method makeButtons (posted here), which is removing all buttons in the screen and adding them again. This works fine in viewDidLoad and viewDidAppear. I am accessing information from a webservice which is telling me I need a new button. When I am calling [self makebuttons] from that method, nothing happends, until I move forth and back with my NavigationController forcing viewDidAppear to do the work again. My question is why? I am doing exactly the same, unless it's not called from viewDidAppear, but from doneGettingInformation.
- (void) viewDidAppear:(bool) animated {
[self makebuttons]; // Works great!
}
- (void) doneGettingInformation : (ASIFormDataRequest *) request {
NSString *response = [request responseString];
[[self.temp.userInfo objectForKey:#"spillider"] addObject:response];
[self makebuttons]; // This gets called, but nothing changes in the view itself.
}
- (void) makeButtons {
NSLog(#"kjort");
int newAntall = [[self.temp.userInfo objectForKey:#"spillider"] count];
for (UIButton * button in gameButtons) {
NSString *tag = [NSString stringWithFormat:#"%i",button.tag];
[button removeFromSuperview];
if ([webviews objectForKey:tag]) {
[[webviews objectForKey:tag] removeFromSuperview];
[webviews removeObjectForKey:tag];
}
}
[gameButtons removeAllObjects];
scroller.contentSize = CGSizeMake(320, 480);
if (newAntall > 3) {
CGSize scrollContent = self.scroller.contentSize;
scrollContent.height = scrollContent.height+((newAntall-3)*BUTTON_HEIGTH);
self.scroller.contentSize = scrollContent;
}
int y = 163;
self.nyttSpillKnapp.frame = CGRectMake(BUTTON_X, y, BUTTON_WIDTH, 65);
for (int i=0; i<newAntall; i++) {
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setBackgroundImage:[UIImage imageNamed:#"knapp_midt"] forState:UIControlStateNormal];
[button setBackgroundImage:[UIImage imageNamed:#"knapp_midt"] forState:UIControlStateHighlighted];
[button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[button.titleLabel setFont:[UIFont systemFontOfSize:15]];
button.frame = CGRectMake(BUTTON_X, y, BUTTON_WIDTH, BUTTON_HEIGTH);
button.enabled = YES;
UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self
action:#selector(deleteButton:)];
swipe.direction = UISwipeGestureRecognizerDirectionRight;
[button addGestureRecognizer:swipe];
button.tag = [[[self.temp.userInfo objectForKey:#"spillider"] objectAtIndex:i] intValue];
NSString * tittel = [NSString stringWithFormat:#"spill %#",[[self.temp.userInfo objectForKey:#"spillider"] objectAtIndex:i]];
[button setTitle:tittel forState:UIControlStateNormal];
UIButton *subButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
subButton.transform = CGAffineTransformMakeRotation(M_PI_2);
subButton.tag = i;
CGRect subframe = CGRectMake(230, 5, subButton.frame.size.width, subButton.frame.size.height);
subButton.frame = subframe;
CGRect myframe = self.nyttSpillKnapp.frame;
myframe.origin.y = myframe.origin.y+BUTTON_HEIGTH;
self.nyttSpillKnapp.frame = myframe;
[subButton addTarget:self action:#selector(clickGameButton:) forControlEvents:UIControlEventTouchUpInside];
[button addSubview:subButton];
[gameButtons addObject:button];
[self.scroller addSubview:button];
y += BUTTON_HEIGTH;
}
}
To sum up, it only works if I am changing viewcontrollers back and forth causing viewWillAppear to get called. Why is that?
I am sorry for my messy methods.
Thanks
If you change the contents of the view outside of the initial view appearing process or layout changes, it's your responsibility to call setNeedsDisplay and inform the run loop that it needs to be redrawn.
The system will ask the view to draw it's contents initially or during layout changes which is why it works as part of the process to first show the view. During that initial process, the viewWill/DidAppear delegates will get called.
From the UIView class reference:
The View Drawing Cycle
View drawing occurs on an as-needed basis. When a view is first shown,
or when all or part of it becomes visible due to layout changes, the
system asks the view to draw its contents. For views that contain
custom content using UIKit or Core Graphics, the system calls the
view’s drawRect: method. Your implementation of this method is
responsible for drawing the view’s content into the current graphics
context, which is set up by the system automatically prior to calling
this method. This creates a static visual representation of your
view’s content that can then be displayed on the screen.
When the actual content of your view changes, it is your
responsibility to notify the system that your view needs to be
redrawn. You do this by calling your view’s setNeedsDisplay or
setNeedsDisplayInRect: method of the view. These methods let the
system know that it should update the view during the next drawing
cycle. Because it waits until the next drawing cycle to update the
view, you can call these methods on multiple views to update them at
the same time.
EDIT:
Also, make sure done getting images is not called on a background thread. You can't edit views on a background thread. If it is you can prepare all the data on a bg thread but then call makeButtons on on the main thread (performSelectorOnMainThread or use blocks.
See GCD, Threads, Program Flow and UI Updating

Resources