if I code these:
UIView *topView = [UIView new];
for (int i = 0; i < 100; i++) {
UIView *childView = [UIView new];
[topView addSubview:childView]
}
Does topView redraw every time when I called the "addSubview" function?
No, topView does not redraw every time. It is simply flagged as "needs display" each time, which is a cheap operation. When you return to the run loop, the run loop will tell topView to actually redraw itself and clear the "needs display" flag.
Related
Can someone please tell me why this doesn't work?:
//Changing the Images
- (void) squareOneColour {
NSUInteger r = arc4random_uniform(5);
[self.squareOne setBackgroundImage:[self.colorArray objectAtIndex:r] forState:UIControlStateNormal];
}
//Moving buttons
- (void) squareOneMover {
NSUInteger r = arc4random_uniform(3);
[self.squareOne setHidden:NO];
CGPoint originalPosition = self.squareOne.center;
originalPosition.y = -55;
[self.squareOne setCenter:originalPosition];
CGPoint position = self.squareOne.center;
position.y += 760;
[UIView animateWithDuration:r + 3
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
[self.squareOne setCenter:position];
}
completion:^(BOOL complete) {
if (complete) {
[self squareOneColour];
[self squareOneMover];
}
}
];
}
I am trying to get a UIButton to animate down the screen and when the animation is finished, for it to repeat but with a different background. The code above seems it should work but the second animation is never completed. (The UIButton is animated once, then everything stops). I have tried putting the code shown below (into the completion block), with an outside NSUInteger method creating a random number for r but it never works. But it does work when I replace r with a different number, like 1 or 2.
I found the answer. After playing around with it, I found that the function I wanted the UIButton to do was only fit for a UIImageView. I guess I'll have to make an invisible button over the UIImageView
You're stepping on your own feet. Step out to the next cycle of the runloop and all will be well:
[self squareOneColour];
dispatch_async(dispatch_get_main_queue(), ^{
[self squareOneMover];
});
I've written a method, sortAndSlideHandCards(), that moves 6 UIButtons. Each UIButton is moved to the same position. This is done via a for-each loop and the animateWithDuration method being called on each UIButton.
This method is called for a number of players at the same time. Currently the behaviour results in UIButtons from each player moving but only one at a time. No more than one UIButton can move at any time, as if each animation is waiting for whatever animation that is currently running to stop before attempting it's own animation, essentially the code is executed sequentially for each player/UIButton. I hoped threading would help me fix this.
when I added the threading code:
backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
sortAndSlideHandCardsGroup = dispatch_group_create();
for(Player* player in _playersArray) {
dispatch_group_async(sortAndSlideHandCardsGroup, backgroundQueue, ^(void) {
[player sortAndSlideHandCards];
});
dispatch_group_wait(sortAndSlideHandCardsGroup,DISPATCH_TIME_FOREVER);
I found that only the first UIButton animation is triggered for each player and that the code gets held up in the runloop "while" because "_animationEnd" never gets set as it would appear the second animation never gets going.
I can see the method launching in its own thread
- (void) sortAndSlideHandCards {
NSLog(#"PLAYER:sortAndSlideHandCards");
CGPoint newCenter;
Card* tempCard = nil;
int count = 1;
float duration = 0.2 / _speedMultiplyer;
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
for(Card *card in _handCards) { //move cards in hand to one postion in hand
if(count == 1) {
tempCard = [[Card alloc] init:_screenWidth:_screenHeight :[card getNumber] :[card getCardWeight] :[card getSuit] :[card getIsSpecial]];
[tempCard setImageSrc: _playerNumber :!_isPlayerOnPhone :count : true :_view: _isAI: [_handCards count]];
newCenter = [tempCard getButton].center;
}
_animationStillRunning = true;
if(![[DealViewController getCardsInPlayArray] containsObject:card] ) {
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionLayoutSubviews animations:^{[card getButton].center = newCenter;} completion:^(BOOL finished){[self animationEnd];}];
while (_animationStillRunning){ //endAnimation will set _animationStillRunning to false when called
//stuck in here after first UIButton when threading code is in play
[runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
} //endAnimation will set _animationStillRunning to false when called
}
count++;
}
}
When i comment out the threading code each UIButton (Card) will animate one after another.
With the threading code is in play the first UIButton will animate but during the second run through the for-loop, the code will be stuck in the while-loop, waiting for the animation to end. I'm guessing the second animation doesn't even start.
I also tried this for the threading code:
[player performSelectorInBackground:#selector(sortAndSlideHandCards) withObject:nil];
Same outcome
Anyone have any ideas why animateWithDuration doesn't like getting called in a loop when in a thread other than the main one?
You should just be able to kick off the animations you want from whatever UI action is performed. The UIView animateWith... methods return immediately, so you don't need to worry about waiting for them to complete.
If you have an unknown number of animations to kick off sequentially, use the delay parameter. Pseudocode:
NSTimeInterval delay = 0;
NSTimeInterval duration = 0.25;
for (UIView *view in viewsToAnimate)
{
[UIView animateWithDuration:duration delay:delay animations:^{ ... animations ...}];
delay += duration;
}
This will increase the delay for each successive animation, so it starts at the end of the previous one.
I am currently iterating through a list and based on the condition of the element I would like to display one of two images. This works fine but how do i put a pause in between displaying the image. I have tried using:
for(
...
[myView addSubview:myImage]
sleep(1)
...
)
after each check but this for some reason waits for the end of the function before displaying any of the images.
Does anyone what the cause of this is and how to get around it?
NSArray *imageArray = [NSArray arrayWithObjects:image1, image2, imageN, nil];
UIImageView *imageView = [[UIImageView alloc] init];
imageView.animationImages = imageArray;
imageView.animationDuration = [imageArray count]*3;
imageView.animationRepeatCount = 0;
[imageView startAnimating];
You can't use sleep() here since it will block the thread that is updating you UI.
What you could do is call a different method that set the second image:
for(
...
[myView addSubview:myImage]
// schedule the method.
[myView performSelector:#selector(addSubview:) withObject:mySecondImage afterDelay:1.0f];
...
)
You could set the alpha/visibility of the element to 0.0f/hidden and use an animation block with a delay to display the view. Gain a fancy animation in the process by setting the duration to something bigger than 0 ;-)
[UIView animateWithDuration: 0.0
delay: 1.0
options: UIViewAnimationCurveEaseOut
animations: ^{
myView.alpha = 1.0f;
}
completion:^(BOOL finished){}];
You can create a function to display an image a use "perform after delay":
-(void)displayImage {
[...get the image...]
[myView addSubview:myImage];
[self performSelector:#selector(displayImage) withObject:nil afterDelay:1.0];
}
I have an array of UIViews which I animate with the following code:
for (int i = 0; i<(int)viewArray.count; i++) {
UIView *view = [viewArray objectAtIndex:i];
CALayer *layer = view.layer;
//animate the layer
}
My question is, is there any way to have a delay between every animation, so that, for example, it animates one UIView in the array and the next animation starts after 0.2 seconds or so? Any help would be greatly appreciated!
You may want to try the following
float timeDelay = 0.2
float duration = YOUR_DURATION
for (int i = 0; i<(int)viewArray.count; i++) {
UIView *view = [viewArray objectAtIndex:i];
//animate the layer
[UIView animateWithDuration:duration delay:(i+1)*timeDelay
options: {Check UIView Class Reference on developer.apple.com}
animations:^{
[view.layer fantastic animation here];
} completion^(BOOL finised){
if(finished){
//Leave blank if nothing, good practice to implement debuging here or post animation processes here (like removing a subview from a super)
}
}];
}
Keep in mind that if u send your program the the background it may break your animations so make sure u recall this method in ur app Delegates:
- (void)applicationWillEnterForeground:(UIApplication *)application;
Hope it helps
Use this guy:
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion
use the second parameter. Inside your loop, call that function, passing an every-increasing number to that parameter.
I have an UITableView with sections in my iPhone application where it looks as something similar to viewing a contact details in standard contacts app. In one of the sections I have a UISegmentedControl set as a header of the section and the items in that section depend on what segment is selected in the segmented control. What I wanted to do is to have animations shifting the rows of one tab out of the view to one side and shifting the rows of another tab in from the other side. I have managed to achieve this by inserting/deleting the respective rows with default UITableView's animations for that but it looks a bit weird when the number of rows in one tab is different from the other one. Also the code for that is quite nasty.
Here's the code I have (simplified a bit):
[self.tableView beginUpdates];
UITableViewRowAnimation delAnim = UITableViewRowAnimationRight;
UITableViewRowAnimation insAnim = UITableViewRowAnimationLeft;
NSUInteger numdel = [self.list count];
NSUInteger numins = [newlist count];
NSMutableArray* indices = [NSMutableArray arrayWithCapacity: numdel];
for (NSUInteger i = 0; i < numins; i++)
{
[indices addObject: [NSIndexPath indexPathForRow: i inSection: SECT]];
}
[self.tableView insertRowsAtIndexPaths: indices withRowAnimation: insAnim];
[indices removeAllObjects];
for (NSUInteger i = 0; i < numdel; i++)
{
[indices addObject: [NSIndexPath indexPathForRow: i inSection: SECT]];
}
[self.tableView deleteRowsAtIndexPaths: indices withRowAnimation: delAnim];
self.list = newlist;
[self.tableView endUpdates];
I'm fairly new to Objective C and Cocoa, so any advices are very welcome.
You could try using a separate table view which you animate in directly. The snippet here is just typed off-the-cuff, so might need some work, but it should give you some pointers at least:
- (void) switchToNewTableFromRight
{
UITableView * newTableView = [[UITableView alloc] initWithFrame: self.tableView.frame style: self.tableView.style];
// put it off to the right of the existing table
CGRect frame = newTableView.frame;
frame.origin.x += frame.size.width;
newTableView.frame = frame;
// set data for new table
// you should ensure you're setup to supply data for the new table here, btw
newTableView.delegate = self;
newTableView.dataSource = self;
[newTableView reloadData];
// add to parent of current table view at this (offscreen) location
[self.tableView.superview addSubview: newTableView];
// now we animate
[UIView beginAnimations: #"TableFromRight" context: newTableView];
// set the function it should call when the animation completes
[UIView setAnimationDelegate: self];
[UIView setAnimationDidStopSelector: #selector(animation:finished:context:)];
// set new table's frame to current table's frame
newTableView.frame = self.tableView.frame;
// set current table's frame to be offscreen to the left
frame = self.tableView.frame;
frame.origin.x -= frame.size.width;
self.tableView.frame = frame;
// commit the animations to start them going
[UIView commitAnimations];
}
- (void) animation: (NSString *) animationID finished: (BOOL) finished context: (void *) context
{
// could be a good idea to check that finished == YES here
UITableView * newTableView = (UITableView *) context;
self.tableView = newTableView;
// newTableView has been inited but not autoreleased, etc.
// now the controller (self) owns it, so release that first reference
[newTableView release];
}
The idea here is that you setup the new table (making it the same size as the existing one), place it offscreen to the right of the existing table, and then animate the movement of both tables left by their width. This way the existing table will shift offscreen while the new one will shift onscreen. When the animation completes it will call the supplied method, giving you the opportunity to make the new table view the official table view.
Another option would be to use a flip transition, which might go something like this:
// setup new table
UITableView * newTableView = [[UITableView alloc] initWithFrame: self.tableView.frame style: self.tableView.style];
newTableView.delegate = self;
newTableView.dataSource = self;
[newTableView reloadData];
[UIView beginAnimations: nil context: NULL];
[UIView setAnimationDuration: 1.0];
[UIView setAnimationTransition: UIViewAnimationTransitionFlipFromRight forView: self.tableView.superview cache: YES];
// generally here you'd remove the old view and add the new view
// I'm *assuming* that UITableViewController's -setTableView: will do the same thing
self.tableView = newTableView;
[UIView commitAnimations];
Hopefully one of those will have the desired effect.