I'm having a bit of trouble. I've set my UIView Animation to repeat itself infinitely, however it does this before the animation starts it's completion function. I want it to wait until after the completion function fully runs because I want to run multiple animations. Any idea how to do this? If not is there a different way I can accomplish the same thing?
I'll post my code below:
-(void) createBlueControlAnimation:(int)messageCount{
if(messageCount == 0) {
return;
}
[blueControl setContentOffset:CGPointMake(0, 0)];
[UIView animateWithDuration:0.5 delay:2 options:UIViewAnimationOptionRepeat
animations:^{
[blueControl setContentOffset:CGPointMake(screenWidth, 0)];
} completion:^(BOOL finished){
if(messageCount >= 2){
[UIView animateWithDuration:0.5 delay:2 options:UIViewAnimationOptionAllowAnimatedContent
animations:^{
[blueControl setContentOffset:CGPointMake(screenWidth * 2, 0)];
} completion:^(BOOL finished){
if(messageCount == 3) {
[UIView animateWithDuration:0.5 delay:2 options:UIViewAnimationOptionAllowAnimatedContent
animations:^{
[blueControl setContentOffset:CGPointMake(screenWidth * 3, 0)];
}completion:^(BOOL finished){
[self scrollAnimationToStart];
}];
}else{
[self scrollAnimationToStart];
}
}];
}else{
[self scrollAnimationToStart];
}
}];
}
-(void) scrollAnimationToStart {
[UIView animateWithDuration:0.5 delay:2 options:UIViewAnimationOptionAllowAnimatedContent
animations:^{
[blueControl setContentOffset:CGPointMake(0, 0)];
} completion:^(BOOL finished){}];
}
So based on the number of messages I want the animation to continue running, and then when it gets to the last message, loop back to the first message and then restart the animation. However right now it just loops infinitely between the first and the second message. Any ideas on how to fix this would be greatly appreciated, thanks.
Firstly: You should create separate consts for anything that is in code.
extern NSTimeInterval const animationDuration;
NSTimeInterval const animationDuration = 0.5;
extern NSTimeInterval const animationDelay;
NSTimeInterval const animationDelay = 2;
Secondly: intent your code in reasonable way, it's quite hard to read what you've pasted. Use one method and push every repeatable part of code into another method.
- (void)createBlueControlAnimation:(NSUInteger)messageCount {
if (messageCount == 0) {
return;
}
__weak typeof(self) weakSelf = self;
[self offsetBlueControl:0];
[UIView animateWithDuration:animationDuration delay:animationDelay options:UIViewAnimationOptionAllowAnimatedContent animations:^{
[weakSelf offsetBlueControl:screenWidth];
} completion:^(BOOL finished) {
if (messageCount < 2) {
[self scrollAnimationToStart];
} else {
[UIView animateWithDuration:animationDuration delay:animationDelay options:UIViewAnimationOptionAllowAnimatedContent animations:^{
[weakSelf offsetBlueControl:screenWidth * 2];
} completion:^(BOOL finished) {
if (messageCount != 3) {
[self scrollAnimationToStart];
} else {
[UIView animateWithDuration:animationDuration delay:animationDelay options:UIViewAnimationOptionAllowAnimatedContent animations:^{
[weakSelf offsetBlueControl:screenWidth * 3];
} completion:^(BOOL finished) {
[self scrollAnimationToStart];
}];
}
}];
}
}];
}
- (void)scrollAnimationToStart {
[UIView animateWithDuration:animationDuration delay:animationDelay options:UIViewAnimationOptionAllowAnimatedContent animations:^{
[blueControl setContentOffset:CGPointMake(0, 0)];
} completion:^(BOOL finished) {}];
}
- (void)offsetBlueControl:(CGFloat)xOffset {
[blueControl setContentOffset:CGPointMake(xOffset, 0)];
}
I should mention here that this animation block is in fact similar everywhere. I'd set it as a separate method also.
Thirdly, keep same way of spacing everywhere (eg check out https://github.com/NYTimes/objective-c-style-guide).
That's all about code, the quality, readibility etc. I'd set it as comment, but there's too much code in my response, so it has to be an answer.
I don't fully understand what do you want to do. As far as I understood:
You have a UI element, called BlueControl.
You want to move this element.
Basing on some counter you want to move your view by some width (btw I advise to switch to NSIntegers from ints and use unsigned option, so NSUInteger etc). The move steps:
3.1. Move button right during 0.5s
3.2. Wait 2 seconds if counter is large enough, otherwise end step 3
3.3. Increase counter, repeat step 3.
You want to repeat the process infinitely.
Is that right? I need to understand the problem to edit this answer and paste a full response here.
Related
I have a method that, via some delegate, manage my app base animation (UIAnimateWithDuration).
This method works fine for so long, everywhere in my app. After updating to Xcode 9, the method still works but in one controller it doesn't.
It seems to generate an infinite loop with the following message:
warning: could not execute support code to read Objective-C class data in the process. This may reduce the quality of type information available.
when [self.view layoutIfNeeded] called.
Here the snippet:
//MyTableViewDelegate.m
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView.contentOffset.y <= 0.) {
[self.presenter gestureAnimationWithConstant:scrollView.contentOffset.y alpha:__alphaMaxContainer];
return;
}
}
//MyPresenter.m
-(void)gestureAnimationWithConstant:(float)y alpha:(double)alpha{
[self.view animationOnScrollingWith:y];
}
//MyViewController.m
-(void)animationOnScrollingWith:(float)constant{
self.constraint.constant += 1;
[self.view layoutIfNeeded]; //here it fires infinite loop
if (self.constraint.constant > - (self.containerView.frame.size.height-10)){
[self constraintAnimationWithAlpha:constant alpha:__maxAlphaContainer enableGesture:NO];
}
}
-(void)constraintAnimationWithAlpha:(float)constant alpha:(double)alpha enableGesture:(BOOL)enableGesture{
[UIView animateWithDuration:0.5 delay:0.0 usingSpringWithDamping:1.0 initialSpringVelocity:1.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.constraint.constant = constant;
[self.view layoutIfNeeded];
[self changeTopSectionAlphaValue:alpha];
} completion:^(BOOL finished) {
if (enableGesture)
self->__gesture.enabled = YES;
[UIView animateWithDuration:0.3 animations:^{
[self.view layoutIfNeeded];
}];
}];
}
Any idea? I'm going mad!
Has anyone experienced the same issue, when calling layoutIfNeeded in animations block freezes UI?
I have a method:
- (void)hide {
dispatch_block_t onMain = ^{
CGFloat height = -viewContent.frame.size.height;
[UIView animateKeyframesWithDuration:0.15f delay:0 options:0 animations:^{
[UIView addKeyframeWithRelativeStartTime:0.f relativeDuration:0.7f animations:^{
self.constraintViewContentBottom.constant = height/3;
[self layoutIfNeeded];
}];
[UIView addKeyframeWithRelativeStartTime:0.7f relativeDuration:0.3f animations:^{
self.constraintViewContentBottom.constant = height;
[self layoutIfNeeded];
}];
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.2f animations:^{
self.viewBackground.alpha = 0.0f;
}
completion:^(BOOL finished) {
self.hidden = YES;
}];
}];
};
if([NSThread isMainThread]) {
onMain();
}
else {
dispatch_sync(dispatch_get_main_queue(), onMain);
}
}
Which works perfectly on the many devices, but on the one iPhone 7, the line: [self layoutIfNeeded]; freezes UI.
I'm not able to debug that device, but can get some logs from it.
Does anyone know how we can solve the problem?
Appreciate any help.
Set the constraints' constants before the animation block. layoutIfNeeded is the only thing that has to be put into the block...
I have this simple animation that fades in several labels, one at a time. But I wonder if it is possible to reduce the code with this logic?
[UIView animateWithDuration:0.50f animations:^{
[_latLabel setAlpha:0.9f];
[_firstLat setAlpha:0.9f];
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.50f animations:^{
[_lonLabel setAlpha:0.9f];
[_firstLon setAlpha:0.9f];
}completion:^(BOOL finished) {
[UIView animateWithDuration:0.50f animations:^{
[_speedLabel setAlpha:0.9f];
[_firstSpeed setAlpha:0.9f];
}completion:^(BOOL finished) {
[UIView animateWithDuration:0.50f animations:^{
[_realNorthLabel setAlpha:0.9f];
[_firstReal setAlpha:0.9f];
}completion:^(BOOL finished) {
[UIView animateWithDuration:0.50f animations:^{
[_magneticNorthLabel setAlpha:0.9f];
[_firstMagnetic setAlpha:0.9f];
}];
}];
}];
}];
}];
Considering the need to perform operation on 2 label objects while executing any single section of the animation block, create a dictionary structure wherein section indexes define the chronological order of the sections animations to be performed & each section index contains an array of label objects.
A simple iterating loop over this structure christened here as dictionaryOfSectionedLabels can help achieve the desired alternate animation implementation.
NSDictionary<NSNumber *, NSArray *> *dictionaryOfSectionedLabels = #{ #0: #[_latLabel, _firstLat],
#1: #[_lonLabel, _firstLon],
#2: #[_speedLabel, _firstSpeed],
#3: #[_realNorthLabel, _firstReal],
#4: #[_magneticNorthLabel, _firstMagnetic]
};
for (NSUInteger i = 0; i < dictionaryOfSectionedLabels.allKeys.count; i++) {
[UIView animateWithDuration:0.5 delay:0.5*i options:UIViewAnimationOptionLayoutSubviews animations:^{
UILabel *firstLabel = [dictionaryOfSectionedLabels[#(i)] firstObject];
UILabel *secondLabel = [dictionaryOfSectionedLabels[#(i)] lastObject];
firstLabel.alpha = 0.9f;
secondLabel.alpha = 0.9f;
} completion:nil];
}
Creating an NSDictionary of your labels can be tedious and confusing code to read in the future. I'd recommend you just create a method.
-(void)customFadeForLabel:(UILabel *)theLabel withDelay:(float)delayAmount {
[UIView animateWithDuration:0.50f
delay:delayAmount
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
[theLabel setAlpha:0.9f];
}completion:nil];
}
Then just call it as needed with delays
[self customFadeForLabel:_latLabel withDelay:0.00f];
[self customFadeForLabel:_firstLat withDelay:0.00f];
[self customFadeForLabel:_lonLabel withDelay:0.50f];
[self customFadeForLabel:_firstLon withDelay:0.50f];
[self customFadeForLabel:_speedLabel withDelay:1.00f];
[self customFadeForLabel:_firstSpeed withDelay:1.00f];
[self customFadeForLabel:_realNorthLabel withDelay:1.50f];
[self customFadeForLabel:_firstReal withDelay:1.50f];
[self customFadeForLabel:_magneticNorthLabel withDelay:2.00f];
[self customFadeForLabel:_firstMagnetic withDelay:2.00f];
I have a method with three animation blocks, which animates two UIImageViews. Will this code cause a stack overflow exception in the future?
I mean recursive blocks. I need to animate them by order. The first big image should fade in, then a smaller image should fade in, and then both images should fade out, and animation should start again.
- (void)startAnimation {
[UIView animateWithDuration:0.3 animations:^{
[_bigImage setAlpha:1];
}
completion:^(BOOL finished) {
[UIView animateWithDuration:0.3 animations:^{
[_mediumImage setAlpha:1];
}
completion:^(BOOL finished1) {
[UIView animateWithDuration:0.2 animations:^{
[_mediumImage setAlpha:0];
[_bigImage setAlpha:0];
}
completion:^(BOOL finished3) {
[self startAnimation];
}];
}];
}];
}
What you posted should work fine. Yes, you could also do it using CAAnimations, but those are harder to figure out.
I disagree with dasdom. I think your current approach is reasonable.
I do find nested animateWithDuration: methods to be hard to figure out. It's easy to get lost in the nested block syntax.
Instead, you could add an integer or enum instance variable that keeps track of the animation step, and have your method invoke different animation code for each step, then call itself again in each animation block:
In your header:
typedef enum
{
first,
second,
third,
done
} animationSteps
#interface MyClass
{
animationSteps currentStep
}
The .m file:
- (void)doAnimation
{
[UIView animateWithDuration:0.3 animations:^
{
switch (currentStep)
{
case first:
[_bigImage setAlpha:1];
break;
case second:
[_mediumImage setAlpha:1];
break;
case third:
[_mediumImage setAlpha:0];
[_bigImage setAlpha:0];
break;
}
}
completion:^(BOOL finished)
{
currentStep++;
if (currentStep == done)
currentStep = first;
[self doAnimation];
}
];
}
I've only worked a bit with UIView animations and blocks, basically one step animations. I'd like to stack a few steps into a sequence. The code below seems to be working but I'm wondering if this is the right approach and/or if there are any issues/limitations with placing blocks within blocks.
Blocks seem great though the code formatting gets sort of unwieldy/unreadable.
CGRect newFrame = CGRectMake(0, 0, 500, 500);
UIView *flashView = [[UIView alloc] initWithFrame:newFrame];
flashView.tag = 999;
[flashView setBackgroundColor:[UIColor grayColor]];
[flashView setAlpha:0.f];
[self.view addSubview:flashView];
[UIView animateWithDuration:.2f
animations:^{
// STEP 1: FADE IN
[flashView setAlpha:1.f];
}
completion:^(BOOL finished){
[UIView animateWithDuration:.9f
animations:^{
// STEP 2: FADE OUT
[flashView setAlpha:0.f];
}
completion:^(BOOL finished){
// STEP 3: CLEAN UP
[flashView removeFromSuperview];
}
];
}];
What you're doing is correct. Sometimes the nesting can get ugly. One alternative for readability is to put each animation in its own method:
-(IBAction)someButtonPressed:(id)sender {
[self saveSomeData];
[self fadeInAndOut];
}
-(void)fadeInAndOut {
[UIView animateWithDuration:.2f
animations:^{
// STEP 1: FADE IN
[self.flashView setAlpha:1.f];
}
completion:[self fadeOut]
];
}
-(void (^)(BOOL))fadeOut {
return ^(BOOL finished) {
[UIView animateWithDuration:.9f
animations:^{
// STEP 2: FADE OUT
[self.flashView setAlpha:0.f];
}
completion:[self cleanUpFlashView]
];
};
}
-(void (^)(BOOL))cleanUpFlashView {
return ^(BOOL finished){
// STEP 3: CLEAN UP
[self.flashView removeFromSuperview];
};
}
That way isn't horrible if you're just nesting once with simple code. If you're going to do something more complex you might try animateWithDuration:delay:options:animations:completion:
using delay: as a way to link the animations. For example:
[UIView animateWithDuration:.2f delay:0
options:UIViewAnimationOptionCurveEaseIn|UIViewAnimationOptionAllowUserInteraction
animations:^{
// STEP 1: FADE IN
[flashView setAlpha:1.f];
}
completion:nil
];
[UIView animateWithDuration:.9f delay:.2f
options:UIViewAnimationOptionCurveEaseIn|UIViewAnimationOptionAllowUserInteraction
animations:^{
// STEP 2: FADE OUT
[flashView setAlpha:0.f];
}
completion:^(BOOL finished){
// STEP 3: CLEAN UP
[flashView removeFromSuperview];
}
];