I have a UIView called sineWave that renders an animated sine wave. I add sineWave as a subview by doing [self.view addSubview:self.sineWave];. I have another UIView called panedView which is the highest/top view/layer. As panedView is dragged up, sineWave view is revealed. I add panedView above sineWave view by doing [self.view insertSubview:panedView aboveSubview:self.sineWave];. When I pan up everything works perfectly. However, since adding the sineWave view I have an issue swiping up. When I swipe up, I can see that the swipe method gets called, but my view doesn't move up where it should. However, if I swipe a second time immediately after my first swipe, my view will move to where its programmed to - revealing the sine wave. Any idea why this would occur?
- (IBAction)handleUpSwipe:(UISwipeGestureRecognizer *)recognizer
{
NSLog(#"Swipe UP");
[UIView animateWithDuration:.1 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
panedView.frame = CGRectOffset(panedView.frame, 0, -1*self.view.bounds.size.height*.80);
} completion:nil];
[self displaySine];
}
-(void) displaySine
{
if(sineIsDisplaying == NO)
{
self.sineWave = [[OSDrawWave alloc] initWithFrame:CGRectMake(-1*self.view.bounds.size.width, (.75*self.view.bounds.size.height), 2*self.view.bounds.size.width, .3125*(2*self.view.bounds.size.width))];
[self.sineWave setBackgroundColor:[UIColor clearColor]];
[self.sineWave animateWave];
[self.view addSubview:self.sineWave];
[self.view insertSubview:panedView aboveSubview:self.sineWave];
sineIsDisplaying = YES;
}
}
Related
How to animate constraint change smoothly with pan gesture in iOS?
I am trying to develop a screen, where a view is at bottom of the screen. And I've added pan gesture to that view. On dragging that view I want change top constraint of that view. Pan gesture is only allowed in vertical and downward direction. I have added some limit for dragging the view. It working but not smoothly. How to animate constraint change smoothly with pan gesture? Here is my code.
- (void)handleGesture:(UIPanGestureRecognizer *)sender
{
CGPoint velocity = [sender velocityInView:_locationContainer];
[sender setTranslation:CGPointMake(0, 0) inView:self.view];
if (fabs(velocity.y) > fabs(velocity.x)) {
NSLog(#"velocity y %f ",velocity.y * 0.13);
if(velocity.y < 0 && (self.locationDetailsTop.constant > minimumTop) )
{
NSLog(#"gesture moving Up");
self.locationDetailsTop.constant = self.locationDetailsTop.constant - fabs(velocity.y * 0.1);
}
else if (self.locationDetailsTop.constant < firstTop)
{
NSLog(#"gesture moving Bottom");
self.locationDetailsTop.constant = self.locationDetailsTop.constant + fabs(velocity.y * 0.1);
}
[self.view layoutIfNeeded];
[UIView animateWithDuration:0.1 animations:^{
[self.mapView setFrame:CGRectMake(0, 0, self.view.frame.size.width, self.locationContainer.frame.origin.y)];
}];
}
}
This is sample image,
My screen is of the same kind like this, But on my screen, there is a map view instead of the calender view
To move view while user is touching a screen, you can use translationInView: property. You can set a translation to current constraint's value and get new value (in a handler of UIGestureRecognizerStateBegan) and change a constraint's constant in a handler of UIGestureRecognizerStateChanged:
- (void)padRecognizerStateChanged:(UIPanGestureRecognizer*)sender
{
if(sender.state == UIGestureRecognizerStateBegan)
{
[sender setTranslation:CGPointMake(0.0, [self getConstraintValue]) inView: _locationContainer];
}
else if (sender.state == UIGestureRecognizerStateChanged)
{
[self setConstraintValue: [sender translationInView:_locationContainer].y];
[self.view setNeedsLayout];
}
}
You can use velocity if you need to move view when a user raised his thumb over the screen for upward or downward movement. For example, you can implement deceleration effect.
If you want it to animate smoothly, try calling layoutIfNeeded inside the animation block like so:
[UIView animateWithDuration:0.1 animations:^{
[self.view layoutIfNeeded];
[self.mapView setFrame:CGRectMake(0, 0, self.view.frame.size.width, self.locationContainer.frame.origin.y)];
}];
I think the original question was asking how to perform this animation smoothly. However, if the map view constraints are linked to the dragged view constraints, due to the polynomial relationship between autolayout constraints and laying out the MKMapView the computation will be quite intense and therefore there will likely be lag. I suggest disconnecting the map constraints from the dragged view constraints if the UI/UX design allows.
I'm trying to maintain existing code and added a new UITextView to a View Controller. All of the views enter the screen by sliding in from the left side. My newly added view also slides in from the left side along with the other views (without me doing anything because the animation is set on their container). The problem is that my view also appears for a second or so before the animation starts. How can I prevent this from happening?
Here is the code for the animation on the container
- (void) showAndAnimateContainerView{
[self.containerView setHidden:NO];
[self hideContainerView:self.containerView];
self.marginLeft.constant = 0;
[UIView animateWithDuration:STD_ANIMATION_TIME delay:ZERO_ANIMATION_TIME options:UIViewAnimationOptionCurveLinear animations:^{
[self.containerView layoutIfNeeded];
} completion:^(BOOL finished) {
}];
}
- (void) hideContainerView:(UIView *) view{
self.widthView.constant = WIDTH_SCREEN;
self.marginLeft.constant = -WIDTH_SCREEN;
[view layoutIfNeeded];
}
Don't know what else I can add to the question to make it more clear.
I have a video player that has a standard toolbar. The toolbar is dismissed by a swipe down gesture. I also have a view (a panel really) that can appear directly above the toolbar and is also dismissed by a swipe down gesture. When both the panel and the toolbar are open, one swipe down gesture should dismiss the panel, a second will dismiss the toolbar. Problem is that when the swipe gestures occur quickly back-to-back (before the panel animation completes) then the toolbar animation jitters.
- (void)handleSwipe:(UISwipeGestureRecognizer *)gestureRecognizer
{
UISwipeGestureRecognizerDirection direction = [gestureRecognizer direction];
if (direction == UISwipeGestureRecognizerDirectionDown) {
if (![toolbar isHidden]) {
// Only dismiss the bottom panel if it is open
if (_selectedSegmentIndex != UISegmentedControlNoSegment) {
_selectedSegmentIndex = UISegmentedControlNoSegment;
[bottomPanelView dismissPanel];
} else {
CGRect tempRect = CGRectMake(0, self.view.frame.size.height, toolbar.frame.size.width, toolbar.frame.size.height);
[UIView animateWithDuration:0.25f
animations:^{
// Move the toolbar off the screen.
toolbar.frame = tempRect;
}
completion:^(BOOL finished) {
[toolbar setHidden:YES];
}];
}
}
}
}
[bottomPanelView dismissPanel] is in a separate class and is not aware of the class that calls it. It has the follow animation...
[UIView animateWithDuration:self.panelAnimationDuration
delay:0.0
options:UIViewAnimationOptionCurveLinear
animations:^{
// slideOutLocation is off the screen
self.view.frame = slideOutLocation;
}
completion:^(BOOL finished) {
[self.view removeFromSuperview];
[self removeFromParentViewController];
self.panelActive = NO;
}];
So basically, the dismissPanel animation is still running when the animation to dismiss the toolbar begins. When performing a double swipe in slow motion in the simulator, the first animation looks fine, but the toolbar animation is jittery.
I know how to nest animations in the completion block, but that cannot be done here since dismissing both the panel and the toolbar is not always what is wanted. Also, the dismissPanel code is handled elsewhere and is not in control of the toolbar.
Is there a way to allow multiple animation blocks to run simultaneously without putting the completion block? Let me know if any clarification is needed! Thanks!
I wonder if the problem might have to do with auto layout (setting frames while auto layout is on causes problems). I tried a simple test of animating a view and a tool bar off the bottom of the screen by animating their constraint constants, and the animation looked fine. I made IBOutlets to their respective bottom constraints (called viewBottomCon and toolBarBottomCon).
- (void)viewDidLoad {
[super viewDidLoad];
self.isFirstSwipe = YES;
}
-(IBAction)downSwipe:(UISwipeGestureRecognizer *)sender {
if (self.isFirstSwipe) {
self.viewBottomCon.constant = -52;
self.isFirstSwipe = NO;
[UIView animateWithDuration:5 animations:^{
[self.view layoutIfNeeded];
} completion:nil];
}else if (!self.isFirstSwipe) {
self.toolBarBottomCon.constant = -44;
[UIView animateWithDuration:3 animations:^{
[self.view layoutIfNeeded];
} completion:nil];
}
}
This is a simpler setup than yours, but I think it should work in your case too.
I am brand new to Core Animation, and I need to know how to do 2 animations:
I need to switch XIBs by fading through black (fully releasing the the first view controller)
I need to mimic the UINavigationController's pushViewController animation (switching XIBs and releasing the first view controller)
How can you achieve these animated view transitions?
I've done both of these animations, but maybe not in the exact way you are looking for.
Fade View to black, I took this the other way an instead added a new
subview that covered the entire window that was Black and animated
the Alpha from 0.0 to 1.0. Made for a nice effect.
[UIView animateWithDuration:0.5
animations:^{ _easterEgg.alpha = 1.0; }
completion:^(BOOL finished) { [self animateIndex:0]; }];
Slide in a view like UINavigationController. I didn't do this exactly like UINavigationController since it does multiple animations, but I did have a new view slide the previous view off screen. This code sets the frame of the new view off screen to the right of the current view, builds a frame location that is off the screen to the left, and grabs the current visible frame. Finally it just animates the new view from off screen right into the visible frame, and the old view from the visible frame to off left. Then removes the old view.
CGRect offRight = CGRectMake(_contentView.frame.size.width,
0,
_contentView.frame.size.width,
_contentView.frame.size.height);
CGRect offLeft = CGRectMake(-_contentView.frame.size.width,
0,
_contentView.frame.size.width,
_contentView.frame.size.height);
CGRect visibleFrame = CGRectMake(0, 0, _contentView.frame.size.width, _contentView.frame.size.height);
[view setFrame:offRight];
UIView *currentView = [[_contentView subviews] lastObject];
[_contentView addSubview:view];
[UIView animateWithDuration:0.5
animations:^{
[currentView setFrame:offLeft];
[view setFrame:visibleFrame];
}
completion:^(BOOL finished) {
[currentView removeFromSuperview];
}];
I'm trying to write an app that uses custom animations to transition between view controllers in the view stack in a UINavigationController.
Right now, I'm adding multiple laters of the same view, just to get the mechanics up and running properly.
My visual goals are as follows:
1. Create a new "FirstVC".
2. Set the alpha value of [FirstVC view] to 0.
3. Set the transform value of [FirstVC view] to 25% in both directions (vertical & horizontal).
In the animations block, what I'd like to accomplish is:
1. Set the transform value of [OutgoingVC view] to 500% in both directions (blowing it up 5x).
2. Set the alpha value of [OutgoingVC view] to 0 (fading out to nothing).
3. Set the transform value of [FirstVC view] to 1 (bringing it back to its original size).
4. Set the alpha value of [FirstVC view] to 1 (fading in to full color).
The net effect of this animation should be that the "top page" blows up and fades out while the "next/bottom page" blows up (only to full screen) and fades in. The idea is of a user falling through a floor or something. "Going deeper."
I've slowed my animation down to a 10.0 sec interval so I can get a better handle of what's going on.
It appears that the animations taking place on "OutgoingVC" (i.e., the top view) are correct. The bottom view, however, seems to come in fine (100% of screen size and alpha=1.0), but then it keeps going and appears to blow up to either 4x/5x and also fade out to an alpha of 0. Once the screen has gone completely black, the new UIViewController (FirstVC) is correctly displayed on screen.
Does anyone see why my code wouldn't behave the way I want?
Thanks!
Stupid me! Here's the code:
- (IBAction)AddNewScreenPressed:(id)sender
{
FirstVC *newViewController = [[FirstVC alloc] init];
UIView *currentView = [self view];
UIView *newView = [newViewController view];
[newView setTransform:CGAffineTransformMakeScale(0.25, 0.25)];
[newView setAlpha:0];
[UIView transitionWithView:[self view]
duration:10.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
[[self view] addSubview:newView];
[currentView setAlpha:0];
[currentView setTransform:CGAffineTransformMakeScale(5.0, 5.0)];
[newView setAlpha:1.0];
[newView setTransform:CGAffineTransformMakeScale(1, 1)];
}
completion:^(BOOL finished){
[[self navigationController] pushViewController:newViewController animated:NO];
}
];
}
You have added newView as a subview of currentView, so perhaps the animations you are doing to currentView also apply to newView - it presumably scales up and changes the alpha of its subviews as well, including newView. Could you have the initial scale of newView to be 0.2, and drop the rescaling of newView in the animation block, so it would end up at 1.0 scale? Not sure what to do about the alpha, though...
EDIT after seeing your solution below - glad you've got it working, would it perhaps be cleaner (if you're grabbing images off the screen to handle your views anyway) to have a separate viewcontroller to manage the transition, passing it the two images so it can do the animations internally, so you push it to the front with no animation, perform your animation starting with the first view's image and ending with the second view's image (so you can just do standard animations on two imageViews instead of the whole view of the controller), and then push the second view controller once you are done?
After playing with it some more, I've gotten over one hump and met with another obstacle.
I'm upvoting jrturton's answer because the problem did indeed seem related to the fact that I was adding the new view to [self view].
What I did to get around this was add a buffer view ([self BackView]) that exists between [self view] and its subviews.
Then, then following code works to push a new view controller:
- (IBAction)AddNewScreenPressed:(id)sender
{
FirstVC *newViewController = [[FirstVC alloc] init];
UIView *newView = [newViewController view];
[newView setAlpha:0];
[newView setTransform:CGAffineTransformMakeScale(0.25, 0.25)];
[[self view] addSubview:newView];
[UIView transitionWithView:[self view]
duration:1.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
[[self BackView] setTransform:CGAffineTransformMakeScale(5, 5)];
[[self BackView] setAlpha:0];
[newView setTransform:CGAffineTransformMakeScale(1, 1)];
[newView setAlpha:1];
}
completion:^(BOOL finished){
[newView removeFromSuperview];
[[self navigationController] pushViewController:newViewController animated:NO];
[[self BackView] setTransform:CGAffineTransformIdentity];
[[self BackView] setAlpha:1];
}
];
}
Now, of course, I've run into another problem, namely, popping controllers.
When I pop them, the animation seems to work just fine, but when I get to the end (completion block) and do the following:
[[self navigationController] popViewControllerAnimated:NO];
or even:
[[self navigationController] popViewControllerAnimatedYES];
what I end up with is a blank white screen.
I'm assuming that something I'm doing during my "push animation" is causing this problem, but I can't figure it out! Help!
Thanks!