I have a UIView that flies from the top when I open that view controller. I've set the Y Constraint of the UIView to -200 and when the view loads, the below is called and everything works fine:
- (void)updateViewConstraints
{
[super updateViewConstraints];
self.popUpViewYConstraint.constant = 37.0f;
self.closeButtonYConstraint.constant = 28.0f;
[self.popUpBaseView setNeedsUpdateConstraints];
[self.closeButton setNeedsUpdateConstraints];
[UIView animateWithDuration:0.25f animations:^{
[self.popUpBaseView layoutIfNeeded];
[self.closeButton layoutIfNeeded];
[self.view layoutIfNeeded];
}];
}
But now I have a close button that should animate the UIView back to the -200 position and then remove the view controller from the screen. But this animation is not taking place. The view controller is getting removed directly. Here's what I'm doing:
- (IBAction)closePressed:(id)sender
{
NSMutableArray *navigationArray = [[NSMutableArray alloc] initWithArray: self.navigationController.viewControllers];
self.navigationController.viewControllers = navigationArray;
[UIView animateWithDuration:2.0f animations:^{
self.popUpViewYConstraint.constant = -200.0f;
[self.popUpBaseView layoutIfNeeded];
} completion:^(BOOL finished){
[navigationArray removeObjectAtIndex: 1];
[self.baseView removeFromSuperview];
[self.view removeFromSuperview];
}];
}
I referred to this link. It seems to be working for them but doesn't work for me.
Please help.
This line:
self.popUpViewYConstraint.constant = -200.0f;
Should be called before the animation block. Also i'm not sure about hierarchy of your views, but make sure you are calling the correct view with layoutIfNeeded call.
How about this
- (IBAction)closePressed:(id)sender
{
NSMutableArray *navigationArray = [[NSMutableArray alloc]:initWithArray:self.navigationController.viewControllers];
self.navigationController.viewControllers = navigationArray;
// I am sure the constant should be set outside the animation block. It won't happen until next run loop, which will be inside the block.
self.popUpViewYConstraint.constant = -200.0f;
// setNeedsUpdateConstraints should be called on the view to which the constraint is added, not the view affected by the constraint.
[self.baseView setNeedsUpdateConstraints];
[UIView animateWithDuration:2.0f animations:^{
// I think this should be topmost view
[self.view layoutIfNeeded];
} completion:^(BOOL finished){
[navigationArray removeObjectAtIndex: 1];
[self.baseView removeFromSuperview];
[self.view removeFromSuperview];
}];
}
Related
My goal is simple and I have achieved this previously which is why I'm resorting to asking my question here.
I am trying to animate a subview. Simple enough. The view ends up exactly where I want it to be after the animation is over. The only problem is:
whatever I do the animation duration is completely ignored. It happens instantly.
- (void)callToDismissView:(UIView *)view {
[self.view addSubview:view];
[self dismissViewControllerAnimated:NO completion:nil];
[UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^(void){
CGRect frame = self.view.frame;
frame.origin.x = frame.size.width;
view.frame = frame;
} completion:^(BOOL finished){
[view removeFromSuperview];
}];
}
Now I have tried different frames and durations to see if the view was animated at all and it always ends up where I need it to be. Only instantly...
EDIT:
The above code is being called by a delegate in the dismissed view controller like so:
- (void)dismissSelf {
if (self.delegate && [self.delegate respondsToSelector:#selector(callToDismissScan)]) {
[self.delegate callToDismissScan];
}
}
And that method itself is triggered by a notification.
I must admit that some things are happening during the transition between the two controllers that I don't fully understand. (And I would very much like to understand them...)
You can try
[self.view layoutIfNeeded];
like this
- (void)callToDismissView:(UIView *)view {
[self.view addSubview:view];
[self dismissViewControllerAnimated:NO completion:nil];
[self.view layoutIfNeeded];
[UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^(void){
CGRect frame = self.view.frame;
frame.origin.x = frame.size.width;
view.frame = frame;
[self.view layoutIfNeeded];
} completion:^(BOOL finished){
[view removeFromSuperview];
}];
}
Maybe you need try like this.
- (void)callToDismissView:(UIView *)view {
[self.view addSubview:view];
CGRect frame = self.view.frame;
frame.origin.x = frame.size.width;
[UIView animateWithDuration:0.3 delay:0.3
options:UIViewAnimationOptionCurveEaseInOut animations:^(void){
view.frame = frame;
}
completion:^(BOOL finished){
[view removeFromSuperview];
//[self dismissViewControllerAnimated:NO completion:nil];//uncomment if need.
}];
}
It seems there was no way in hell I could animate the transition in the presenting view controller and so the simplest way to resolve the issue was to animate the transition in the presented view controller before it is being dismissed.
The way I did this was by casting the delegate to a view controller. It was then easy to add it's view as a sub-view of my presented view controller and animate it like so:
- (void)dismissSelf {
UIView *dummyView = [ScreenCapper captureScreenIn: self];
UIViewController *presentingViewController = (UIViewController *)self.delegate;
[self.view.window addSubview: presentingViewController.view];
[self.view.window addSubview: dummyView];
[UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^(void){
dummyView.frame = CGRectMake(dummyView.frame.size.width, dummyView.frame.origin.y, dummyView.frame.size.width, dummyView.frame.size.height);
} completion:^(BOOL finished){
if (self.delegate && [self.delegate respondsToSelector:#selector(callToDismissView)]) {
[self.delegate callToDismissView];
}
}];
}
And in the presenting view controller all that was left to do was to dismiss the presented view controller without animations.
- (void)callToDismissView {
[self dismissViewControllerAnimated:NO completion:nil];
}
We have a viewcontroller which has two modes of view - list and map.
Earlier we were doing the toggle by using our custom tabcontroller. But now, as we didnt want to show tabs and do some more things, we are doing it by adding childviewcontrollers.
But there are few subviews which are common in both child view, like filters.
So, this is how we are doing it now:
- (void)switchView:(UIButton *)sender {
if(!self.mapVisible) {
self.mapVisible = YES;
_mapViewController.view.frame = self.view.bounds;
[self addChildViewController:_mapViewController];
[_listViewController willMoveToParentViewController:nil];
[self transitionFromViewController:_listViewController
toViewController:_mapViewController
duration:0.7
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:nil
completion:^(BOOL done){
[_mapViewController didMoveToParentViewController:self];
[_listViewController removeFromParentViewController];
[self setupFooterAfterFlip];
}];
} else {
self.mapVisible = NO;
_listViewController.view.frame = self.view.bounds;
[self addChildViewController:_listViewController];
[_mapViewController willMoveToParentViewController:nil];
[self transitionFromViewController:_mapViewController
toViewController:_listViewController
duration:0.7
options:UIViewAnimationOptionTransitionFlipFromRight
animations:nil
completion:^(BOOL done){
[_listViewController didMoveToParentViewController:self];
[_mapViewController removeFromParentViewController];
[self setupFooterAfterFlip];
}];
}
}
We call the switchview method every time we toggle between views. As you can see, we are setting up the footer everytime from scratch. Is their a more efficient way so that self.view keeps the footer and only toggles between listview and mapview?
I figured out a possible solution. Rather than calling the method setupFooterAfterFlip every time, and creating filters from scratch, I just gave filters a tag id. Now, every time user toggles the view, I remove filters from superview and add it again to new view.
// remove footer
UIView *footer = (UIView *)[self.view viewWithTag:22];
[footer removeFromSuperview];
//change view, write some code, add footer again
[self.view addSubview:footer];
I am trying to animate a UIWebView in from the bottom on top of my viewController, and I am using masonry (https://github.com/Masonry/Masonry).
I initially create my webview with the size of 0 - (x,y,height and width), and I then try to animate it so that the webview animates "on top" of the viewcontroller. The webview is shown, but it doesn't animate in place - it just appears immediately.
Can anyone who has experience guide me in the right direction?
This is my button action
-(void)didPressBtn{
self.infoView = [UIWebView new];
self.infoView.scalesPageToFit = YES;
self.infoView.backgroundColor = [UIColor whiteColor];
self.infoView.delegate = self;
[self.scrollView addSubview:self.infoView];
[self.infoView makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(#(0));
}];
[self.scrollView layoutIfNeeded];
//FIXME: Hmmm it doesn't really animate!?
[UIView animateWithDuration:1 animations:^{
[self.scrollView setContentOffset:CGPointMake(0, 0)];
[self.infoView makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.scrollView);
}];
[self.scrollView layoutIfNeeded];
} completion:^(BOOL finished) {
[self.infoView loadRequest:[[NSURLRequest alloc] initWithURL:[[NSURL alloc] initWithString:NSLocalizedString(#"INFORMATION_URL", )] cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:15]];
}];
}
My viewDidLoad
- (void)viewDidLoad
{
[super viewDidLoad];
self.scrollView = [UIScrollView new];
self.scrollView.backgroundColor = [UIColor whiteColor];
[self.view addSubview:self.scrollView];
[self.scrollView makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view);
}];
UIView *contentView = [UIView new];
[self.scrollView addSubview:contentView];
[contentView makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.scrollView);
make.width.equalTo(self.scrollView);
}];
//Then adding buttons and such...
}
One quick observation regarding didPressButton. You are using mas_makeConstraints to set the edges equal to #0 and them immediately afterwards using mas_makeConstraints again to change them so that the scrollview is filled. This adds contradictory constraints on top of the first set.
Instead, you can use mas_remakeConstraints to replace the existing constraints on that view.
As for the animation, the pattern I use is to first update the constraints (not inside an animation block) and then animate a layout:
[UIView animateWithDuration:1
animations:^{
[theParentView layoutIfNeeded];
}];
See also How do I animate constraint changes?
Fixed
It turns out the animation was being called twice. I've now fixed it so the animation is only called once and it works perfectly.
I suspect it might be something related to my use of child view controllers, but I'm noticing some strange behaviour when I try to remove the child view controller's view with an animation.
I'm adding the child view controller to the parent, along with the child view controller's view, and animating that view from the bottom of the screen. This works perfectly — my issue is when I try to remove the child view/view controller. I'm animating the child's view to the bottom of the screen, and then calling [self.view removeFromSuperview] in the completion block, but the view is removed immediately (so no animation takes place). If I delete the [self.view removeFromSuperview] line, the animation works correctly, but then the view isn't removed from the parent view controller's view.
Adding the child view controller
(this works as intended)
ChildViewController *myChildViewController = [[ChildViewController alloc] init];
myChildViewController.view.frame = CGRectMake(0.f, self.view.frame.size.height, self.view.frame.size.width, self.view.frame.size.height - 64.f);
[self addChildViewController:myChildViewController];
[self.view addSubview:myChildViewController.view];
[myChildViewController didMoveToParentViewController:self];
[UIView animateWithDuration:0.75f delay:0.f usingSpringWithDamping:0.6f initialSpringVelocity:0.75f options:0 animations:^{
myChildViewController.view.frame = CGRectMake(0.f, 64.f, self.view.frame.size.width, self.view.frame.size.height - 64.f);
} completion:nil];
Removing the child view controller
(this doesn't work — the view is removed immediately, instead of after completion)
[UIView animateWithDuration:0.75f delay:0.f usingSpringWithDamping:0.6f initialSpringVelocity:0.75f options:0 animations:^{
self.view.frame = CGRectMake(0.f, self.view.superview.frame.size.height, self.view.frame.size.width, self.view.frame.size.height);
} completion:^(BOOL finished) {
if (finished)
{
[self willMoveToParentViewController:self.parentViewController];
[self.view removeFromSuperview];
[self removeFromParentViewController];
}
}];
I've even tried removing [self willMoveToParentViewController:self.parentViewController] and [self removeFromParentViewController] just to see if that changed anything, but as long as [self.view removeFromSuperview] is there, the view disappears immediately.
Add the in the Parent class
- (void) moveToAddJobVC:(NSString*)storyboardId {
ChildViewController *destVC = [self.storyboard instantiateViewControllerWithIdentifier:storyboardId];
destVC.completionHandler = ^(NSString* string) {
NSLog(#"Returned strng.....%#",string);
//[self viewWillAppear:YES];
};
[destVC.view setFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
[self addChildViewController:destVC];
[self.view addSubview:destVC.view];
[destVC didMoveToParentViewController:self];
[UIView animateWithDuration:0.3 animations:^{
[destVC.view setFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
}];
}
And in child VC write
#property (nonatomic,copy) void (^completionHandler)(NSString *string);
- (IBAction)dismissBtnAction:(id)sender{
if (self.completionHandler)
{
self.completionHandler(#"Return data");
}
[self removeFromParentViewController];
[self.view removeFromSuperview];
}
I have a View (CupsViewController) with a Label and when it's clicked, TableView (AddressTableController) slides from the bottom of the screen and stays in the lower half.
In TableView, when Ok Buttonis pressed, I want to change value of Labeland remove TableViewwith animation (that is, slide to the bottom).
Here is my code:
CupsViewController
Method called when click on Label
- (IBAction)showPop:(id)sender
{
addressTableController = [[AddressTableController alloc]initWithStyle:UITableViewStyleGrouped];
[addressTableController setDelegate:self];
[[self view] addSubview:[addressTableController myTableView]];
[UIView animateWithDuration:1.0 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
[[addressTableController myTableView] setFrame:CGRectMake(0, kScreenHeight * 0.5, kScreenWidth, kScreenHeight * 0.5)];
} completion:nil];
}
Method called from AddressTableControllerwhen Buttonis pressed
- (void)addressTableController:(AddressTableController *)viewController didChooseValue:(int)value {
direccionLabel.text = [[[[myAppDelegate usuarioActual] cups] objectAtIndex:value] direccion];
[addressTableController.view removeFromSuperview];
}
Image
As you can see, I've tried with removeFromSuperviewbut it does nothing. How can I slide TableViewto the bottom?
It seems that you myTableView property returns a different view, not the one that is referenced in the view property. So you basically add the former as a subview ([[self view] addSubview:[addressTableController myTableView]];), but try to remove the latter ([addressTableController.view removeFromSuperview];).
Just do
[[addressTableController myTableView] removeFromSuperview];
instead of
[addressTableController.view removeFromSuperview];
Here you go. Cheers, mate! :)
P.S.
if you want to animate the view out, you can do it like this
[UIView animateWithDuration:1.0 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
// animation code...
} completion:^(BOOL finished) {
[[addressTableController myTableView] removeFromSuperview];
}];