I am trying to translate this code in my solution to enable a view to gracefully fade when clicked:
[UIView transitionWithView:button
duration:0.4
options:UIViewAnimationOptionTransitionCrossDissolve
animations:NULL
completion:NULL];
button.hidden = YES;
But I can't find the equivalent of transitionWithView under UIView in Xamarin, can someone advise?
I tried this for a different animation but it does not give the option of dissolving:
UIView.SetAnimationTransition(UIViewAnimationTransition.CurlDown, button, true);
Setting the hidden property will just hide the UIView. You can use the following code to animate out your view, and use the callback to execute logic when the animation is done.
UIView.Animate (2, 0, UIViewAnimationOptions.CurveLinear,
() => {
button.Alpha = 0.0f;
},
() => {
// animation complete logic
}
);
The code above will fade out the button view over 2 second, with a delay of 0 seconds using a linear curve.
UIView.Transition(button, 0.4, UIViewAnimationOptions.TransitionCrossDissolve, null,
() => {
button.Hidden = true;
});
UIKit.UIView.Transition
Ref: https://developer.xamarin.com/api/member/UIKit.UIView.Transition/p/UIKit.UIView/System.Double/UIKit.UIViewAnimationOptions/System.Action/System.Action/
Related
I am using transitionWithView animation block to animate view. This block is called when user swipe the view in left or right direction. It works fine. But I want to create my own custom animation like view gets changed from left to right direction. I don't know how to achieve this. I am using UIViewAnimationOptionTransitionCurlUp for right to left swipe
and UIViewAnimationOptionTransitionCurlDown for left to right swipe in UIViewAnimationOptions.
[UIView transitionWithView:self.view duration:0.6 options:UIViewAnimationOptionTransitionCurlUp animations:^{
setDataInLayout();
} completion:^(BOOL finished) {
[scrollViewContent setContentOffset:CGPointZero animated:YES];
[self.delegate updatePaginationCounter:_currentStoneIndex];
}];
You have to implement this animation by yourself. I would use UIView animation API like:
UIView.animateWithDuration(0.6, animations: {
// apply 2D transformation to the view, or use any other animatable property
self.view.transform = CGAffineTransformMakeRotation(fullRotation)
} completion: {finished in
// you completion block
})
Here is the list of UIView's animatable properties. For more complex animations you can use the keyframe-based animation:
UIView.animateKeyframesWithDuration(duration, delay: delay, options: options, animations: {
// add your keyframe animations here
UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: 1/2, animations: {
self.view.transform = ...
})
UIView.addKeyframeWithRelativeStartTime(1/3, relativeDuration: 1/2, animations: {
self.view.transform = ...
})
}, completion: {finished in
// completion block
})
}
I want to animate my UIProgressView progression from 0 to 1 during 10 seconds.
Code:
[UIView animateWithDuration:10.0 animations:^{
[_myProgressView setProgress:1 animated:YES];
} completion:(BOOL finished)^{
if (finished) NSLog(#"animation finished);
}];
Animation is working fine except that completion and NSLog are always called instantly.
I tried animateWithDuration: withDelay: but the delay is not respected and executed immediately.
Has anyone encountered the same problem?
Thanks for you help.
Its an old problem but I am still posting the solution here. The solution turned out to be really simple.
All you need to do is:
[_myProgressView setProgress:1];
[UIView animateWithDuration:10.0 animations:^{
[_myProgressView layoutIfNeeded];
} completion:^(BOOL finished){
if (finished) NSLog(#"animation finished");
}];
Here is a good explanation for why things were working incorrectly for you:
http://blog.spacemanlabs.com/2012/08/premature-completion-an-embarrassing-problem/
So you can not use setProgress:animated: method to animate the progress view and get your desired behaviour of completion block as well. However, just setting progress inside the block will not animate it, since progress is not an animatable property.
Refer to apple documentation which says its necessary for the property to be animatable:
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/#//apple_ref/occ/clm/UIView/animateWithDuration:animations:
So you just update the value of progress property outside the animate block and do the layout of the view inside the animate block.
None of these answers really worked for me. I was able to get the progress to animate, but for some reason it had a delay in the beginning (especially for longer durations).
The key for me, was to call layoutIfNeeded inside an animation block and wait on the completion before doing the 2nd animation. This code is called from viewDidLoad. With the following code, I was able to achieve a perfectly smooth timer animation.
// For some reason, the first layoutIfNeeded() needs to be wrapped in an animation block, even though it is supposedly a synchronous call. Otherwise, the progress just jumps right to 100%
UIView.animate(withDuration: 0.0, animations: {
self.progressView.layoutIfNeeded()
}, completion: { finished in
self.progressView.progress = 1.0
UIView.animate(withDuration: self.timerDuration, delay: 0.0, options: [.curveLinear], animations: {
self.progressView.layoutIfNeeded()
}, completion: { finished in
print("animation completed")
})
})
Make it simple instead using NSTimer like,
[progressview setProgress:0 animated:NO];
NSTimer *t=[NSTimer scheduledTimerWithTimeInterval: 1.0f
target: self
selector: #selector(progressupdate)
userInfo: nil
repeats: YES];
Below is the function which updates progress,
-(void)progressupdate{
if(progressview.progress<1){
[progressview setProgress:(progressview.progress+=0.2) animated:YES];
}
else{
[t invalidate];
}
}
Hope it helps....
What i found out is a set of both UIview animations and progress view animatons that work much nicer and animate the progressview smoothly. If you just use the combination of animation and progressview animation it is fired directly without regard to the UIview animation timer.
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
timer = [NSTimer scheduledTimerWithTimeInterval: 1.0f
target: self
selector: #selector(updateTimer)
userInfo: nil
repeats: YES];
}
- (void)updateTimer
{
if (progressView.progress >= 1.0) {
[timer invalidate];
}
[UIView animateWithDuration:1 animations:^{
float newProgress = [self.progressView progress] + 0.125;
[self.progressView setProgress:newProgress animated:YES];
}];
}
Feel free to adjust the animation times to even better and smooth transitions
It's an old question, but I had a similar problem and here is my solution (sorry it's in Swift). The completion part is called when the animation finishes. It's inside a custom UIProgressView class.
override func setProgress(progress: Float, animated: Bool) {
self.progress = progress
if animated {
let duration: NSTimeInterval = 10.0
UIView.animateWithDuration(duration, delay: 0.0, options: UIViewAnimationOptions.CurveLinear, animations: {
self.layoutIfNeeded()
}, completion: { (completed) in
self.animationDuration = nil
})
} else {
self.layoutIfNeeded()
}
}
Trying here develop a side menu, but i'm facing a problem that i cannot figure out why is happening.
Here is a video sample to show what the problem is:
https://www.dropbox.com/s/i97iigus8gj2sl4/problem_side_menu.mov
When I press the menu button the labels appear first than the view controller, what I want is that the latest follow the transition animation.
I have a container view controller that manages all my views controllers.
Already did some research, but I really don't know which keywords use to find useful information.
I really appreciate any help you can provide.
The animation suggests that you have drawn your buttons on wrong view. Make sure your side menu is owner of your button.
What I would recommend doing is fading the UILabels/UIButtons as the transition happens. So in your code that hides the side menu container, do the animations there.
[UIView animateWithDuration:0.2
delay:0.0
options:UIViewAnimationOptionCurveLinear
animations:^
{
self.label.alpha = 0.0;
self.button.alpha = 0.0;
}
completion:^(BOOL finished)
{
if (finished)
{
self.label.alpha = 1.0;
self.button.alpha = 1.0;
}
}];
I am using [UIView animateWithDuration... ] in order to display a text for each page of my app. Each page got its own text. I'm swiping to navigate between pages. I am using a 1 second dissolve effect to have the text fading in after the page is displayed.
Here's the problem: If I'm swiping during that 1 second (during which the text is fading in) the animation will complete when the next page appears and 2 texts will overlap (the previous and the current).
The solution I'd like to implement is to interrupt the animation if I happen to swipe during its occurrence. I just cannot make it happen. [self.view.layer removeAllAnimations]; does not work for me.
Here's my animation code:
- (void) replaceContent: (UITextView *) theCurrentContent withContent: (UITextView *) theReplacementContent {
theReplacementContent.alpha = 0.0;
[self.view addSubview: theReplacementContent];
theReplacementContent.alpha = 0.0;
[UITextView animateWithDuration: 1.0
delay: 0.0
options: UIViewAnimationOptionTransitionCrossDissolve
animations: ^{
theCurrentContent.alpha = 0.0;
theReplacementContent.alpha = 1.0;
}
completion: ^(BOOL finished){
[theCurrentContent removeFromSuperview];
self.currentContent = theReplacementContent;
[self.view bringSubviewToFront:theReplacementContent];
}];
}
Do you guys know how to make this work? Do you know any other way to address this issue?
You can not directly cancel animations created via +animateWithDuration.... What you want to do is replace the running animation with an instant new one.
You could write the following method, that get's called when you want to show the next page:
- (void)showNextPage
{
//skip the running animation, if the animation is already finished, it does nothing
[UIView animateWithDuration: 0.0
delay: 0.0
options: UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionBeginFromCurrentState
animations: ^{
theCurrentContent.alpha = 1.0;
theReplacementContent.alpha = 0.0;
}
completion: ^(BOOL finished){
theReplacementContent = ... // set the view for you next page
[self replaceContent:theCurrentContent withContent:theReplacementContent];
}];
}
Notice the additional UIViewAnimationOptionBeginFromCurrentState passed to options:. What this does is, it basically tells the framework to intercept any running animations for the affected properties and replace them with this one.
By setting the duration: to 0.0 the new values are set instantly.
In the completion: block you can then create and set your new content and call your replaceContent:withContent: method.
So, another possible solution would be to just disable interaction during the animation.
[[UIApplication sharedApplication] beginIgnoringInteractionEvents];
[[UIApplication sharedApplication] endIgnoringInteractionEvents];
I would declare a flag like shouldAllowContentToBeReplaced. Set it to false when the animation starts and back true when it's complete. Then say if (shouldAllowContentToBeReplaced) { before you start the animation.
I'm running this code...
[UIView animateWithDuration:0.2
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
CGAffineTransform settingsTransform = CGAffineTransformMakeTranslation(self.settingsView.frame.size.width, 0);
CGAffineTransform speedTransform = CGAffineTransformMakeTranslation(-self.speedView.frame.size.width, 0);
self.settingsView.transform = settingsTransform;
self.speedView.transform = speedTransform;
} completion:nil];
But when it runs the views jump half the transform in the opposite direction before sliding to half a position in the correct direction.
I've slowed down the animation duration to 5 seconds but the initial jump is instantaneous and half the transformation in the wrong direction.
When I animate back using this code...
[UIView animateWithDuration:0.2
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
self.settingsView.transform = CGAffineTransformIdentity;
self.speedView.transform = CGAffineTransformIdentity;
} completion:nil];
It does exactly the same thing.
The result is that the final movement is half the desired transform as it jumps half the transform in the wrong direction first.
I really can't work out why this is happening?
Any ideas.
::EDIT::
Clearing up some possible ambiguity.
I'm not trying to have these views "bounce" back to where they are. The views I'm animating are like control panel at the edge of the screen. When the user presses "go" the view then slide out of the way. When the user presses "stop" the panels slide back into the view.
At least they used to. Since enabling auto layout (which I need for other parts of the app) I can't just change the frame of the views so I went the the transform route.
You can see this effect by sticking a view into a view controller and a button to run the animation.
Thanks
I had the exact same problem so here is the solution I came up with.
CGAffineTransform transform = CGAffineTransformMake(1, 0, 0, 1, translation.x, translation.y);
[UIView animateWithDuration:0.25 animations:^{
_assetImageView.transform = transform;
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
}];
So I call [self.view layoutIfNeeded]; inside of the animate block. Without this the it has the same problem as you and jumps the distance negative translation first then animates to the correct position from there. I am calling this from a view controller so self is a sub class of UIViewController. In your case "self.view" may not exist but I hope you get the idea.
None of the solutions here worked for me... My animation would always skip. (except when it was right at the end of another animation, hint).
Some details:
The view that was doing this had a series of scale and translates already in the stack.
Solution:
Doing a keyframe animation, with a super short first key that would basically re apply the transform that that the last animation should have set.
UIView.animateKeyframesWithDuration(0.4, delay: 0.0, options: [.CalculationModeCubic], animations: { () -> Void in
//reset start point
UIView.addKeyframeWithRelativeStartTime(0.0, relativeDuration: 0.01, animations: { () -> Void in
self.element.transform = initialTransform
})
UIView.addKeyframeWithRelativeStartTime(0.01, relativeDuration: 0.99, animations: { () -> Void in
self.element.transform = finalTransform
})
}, completion: nil)
OK, having watched the WWDC videos again they state that one of the first things you have to do when using AutoLayout is to remove any calls for setFrame.
Due to the complexity of the screen I have removed the Auto-Layout completely and I'm now using the frame location to move the view around the screen.
Thanks
I've asked Apple Technical Support about this issue and got this response, so it's a bug.
iOS 8 is currently exhibiting a known bug in UIKit animations where
transform animations get the wrong fromValue (primarily affecting the
position of animated objects, making them appear to “jump”
unexpectedly at the start on an animation).
[...]
The workaround until a potential fix is delivered is to drop down to
Core Animation APIs to code your animations.
Here's improved version of Andreas answer
The key difference is that you can specify just one keyframe and get less code
UIView.animateKeyframes(withDuration: animationDuration, delay: 0.0, options: [], animations: {
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 1.0, animations: {
animatedView.transform = newTransform
})
}, completion: nil)
Does your settingsView or speedView already have a transformation applied before this animation takes place?
If the transformations for these views is not the identity transformation (aka CGAffineTransformIdentity, aka no transformation), you cannot access their .frame properties.
UIViews' frame properties are invalid when they have a transformation applied to them. Use "bounds" instead.
Since you want your second animation to occurs from the current state of your first animation (whether it is finished or not) I recommend to use the UIViewAnimationOptionLayoutSubviews option when setting your second animation.
[UIView animateWithDuration:0.2
delay:0.0
options:UIViewAnimationOptionCurveEaseOut|UIViewAnimationOptionLayoutSubviews
animations:^{
self.settingsView.transform = CGAffineTransformIdentity;
self.speedView.transform = CGAffineTransformIdentity;
} completion:nil];
Here is a sample of the code i used for testing by using the simple view controller template:
myviewcontroller.m:
- (void)viewDidLoad
{
[super viewDidLoad];
self.animatedView = [[UIView alloc] initWithFrame:CGRectMake(20, 20, 160, 80)];
self.animatedView.backgroundColor = [UIColor yellowColor];
[UIView animateWithDuration:0.2
delay:0.0
options:UIViewAnimationOptionCurveEaseOut|UIViewAnimationOptionLayoutSubviews
animations:^{
CGAffineTransform settingsTransform = CGAffineTransformMakeTranslation(self.animatedView.frame.size.width, 0);
self.animatedView.transform = settingsTransform;
}
completion:nil];
self.buttonToTest = [UIButton buttonWithType:UIButtonTypeRoundedRect];
self.buttonToTest.frame = CGRectMake(90, 20, 80, 40);
[self.buttonToTest setTitle:#"Click Me!" forState:UIControlStateNormal];
[self.buttonToTest addTarget:self action:#selector(buttonClicked) forControlEvents:UIControlEventTouchUpInside];
// set-up view hierarchy
[self.view addSubview:self.buttonToTest];
[self.view addSubview: self.animatedView];
}
- (void) buttonClicked
{
[UIView animateWithDuration:.2
delay:0.0
options:UIViewAnimationOptionCurveEaseOut|UIViewAnimationOptionBeginFromCurrentState|UIViewAnimationOptionLayoutSubviews
animations:^{
self.animatedView.transform = CGAffineTransformIdentity;
}
completion:nil];
}
I had this problem and solved it by:
Hide the original view
Add a temp view that copies the view you want to animate
Carry out animation which will now be as intended
Remove the temp view and unhide the original view with the final state
Hope this helps.
I tried the answers above, about the layoutIfNeeded, but it didn't work.
I added the layoutIfNeeded outside (before) the animation block and it solved my problem.
view.layoutIfNeeded()
UIView.animate(withDuration: duration, delay: 0, options: .beginFromCurrentState, animations: {
// Animation here
})