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?
Related
I'm trying to show a fullscreen picture from a smaller one included in a custom UITableViewCell. My code is highly linked to this article
By the way, in this example, the frame : [[UIScreen mainScreen] bounds]is not the good one for me. It's the an UIScrollView's bounds that I've got. I this to add the main screen through a variable inside the cell directly when each cell is created. So I've customized the previous example like this :
//viewDidLoad
self.globalView.frame = [[UIScreen mainScreen] bounds];
//cellForRowAtIndexPath
[cell setFullScreenView:self.globalView];
//fullScreenMethod
if (!isFullScreen) {
[UIView animateWithDuration:0.5 delay:0 options:0 animations:^{
//save previous frame
prevFrame = imageView.frame;
[imageView setFrame:self.fullScreenView.frame];
}completion:^(BOOL finished){
isFullScreen = YES;
}];
return;
}
My problem is that the imageView's new frame is not a full screen but still the UIScrollView's one.
Thank you for your help !
Best approach is create one temporary UIImageView and Show it in full screen,
For animation simply add the temporary UIImageView to location where the image view exists and animate it to full-screen and do revers for normal
Add tap gesture to UIImageView and add this bannerTapped as selector
//This will create a temporary imaget view and animate it to fullscreen
- (void)bannerTapped:(UIGestureRecognizer *)gestureRecognizer {
NSLog(#"%#", [gestureRecognizer view]);
//create new image
temptumb=(UIImageView *)gestureRecognizer.view;
//fullview is gloabal, So we can acess any time to remove it
fullview=[[UIImageView alloc]init];
[fullview setContentMode:UIViewContentModeScaleAspectFit];
[fullview setBackgroundColor:[UIColor colorWithPatternImage:[UIImage imageNamed:#"bckgrd.png"]]];
fullview.image = [(UIImageView *)gestureRecognizer.view image];
CGRect point=[self.view convertRect:gestureRecognizer.view.bounds fromView:gestureRecognizer.view];
[fullview setFrame:point];
[self.view addSubview:fullview];
[UIView animateWithDuration:0.5
animations:^{
[fullview setFrame:CGRectMake(0,
0,
self.view.bounds.size.width,
self.view.bounds.size.height)];
}];
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(fullimagetapped:)];
singleTap.numberOfTapsRequired = 1;
singleTap.numberOfTouchesRequired = 1;
[fullview addGestureRecognizer:singleTap];
[fullview setUserInteractionEnabled:YES];
}
//This will remove the full screen and back to original location.
- (void)fullimagetapped:(UIGestureRecognizer *)gestureRecognizer {
CGRect point=[self.view convertRect:temptumb.bounds fromView:temptumb];
gestureRecognizer.view.backgroundColor=[UIColor clearColor];
[UIView animateWithDuration:0.5
animations:^{
[(UIImageView *)gestureRecognizer.view setFrame:point];
}];
[self performSelector:#selector(animationDone:) withObject:[gestureRecognizer view] afterDelay:0.4];
}
//Remove view after animation of remove
-(void)animationDone:(UIView *)view
{
//view.backgroundColor=[UIColor clearColor];
[fullview removeFromSuperview];
fullview=nil;
}
It can't take a full screen frame because its parent view is the scrollView. Either show view modally or somehow move imageView directly under view controller's main view.
I am trying to Display a an Image when the user Taps on the UIImageView. But before the user Taps the UIImageView the Image should not be shown, and after a few seconds the Image should disappear again. Does anyone know how to do this? I read through couple of Threads but they do not work with the latest Xcode as it appears. Thanks for your help and time.
Udate
Well, my code now looks like this:
-(void)imageTapped:(UITapGestureRecognizer*)recognizer
{
recognizer.view.alpha=0.0;
((UIImageView*)recognizer.view).image = [UIImage imageNamed:#"twingo_main.png"];
[UIView animateWithDuration:1.0 delay:2.0 options:0 animations:^{
recognizer.view.alpha=1.0;
} completion:^(BOOL finished) {
recognizer.view.alpha=0.0;
((UIImageView*)recognizer.view).image = nil;
recognizer.view.alpha=1.0;
}];
}
- (void)viewDidLoad
{
UIImageView *hiddenImage = [[UIImageView alloc] initWithFrame:CGRectMake(0, 30, 20, 20)];
hiddenImage.userInteractionEnabled=YES;
[hiddenImage addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(imageTapped:)]];
[self.view addSubview:hiddenImage];
Well, now my question is, how do I need to set up the UIImageView in the View Controller?
In your UIViewController's viewDidLoad method:
...
UIImageView *hiddenImage = [[UIImageView alloc] initWithFrame:CGRectMake(0, 30, 20, 20)];
hiddenImage.userInteractionEnabled=YES;
[hiddenImage addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(imageTapped:)]];
[self.view addSubview:hiddenImage];
...
UITapGestureRecognizer Handler:
-(void)imageTapped:(UITapGestureRecognizer*)recognizer
{
recognizer.view.alpha=0.0;
((UIImageView*)recognizer.view).image = [UIImage imageNamed:#"imageName"];
[UIView animateWithDuration:1.0 delay:2.0 options:0 animations:^{
recognizer.view.alpha=1.0;
} completion:^(BOOL finished) {
recognizer.view.alpha=0.0;
((UIImageView*)recognizer.view).image = nil;
recognizer.view.alpha=1.0;
}];
}
Set the image in the UIImageView to nil initially. Add a tap gesture recognizer to the UIImageView that, when fired, sets the image and starts a timer. When the timer completes, set your image back to nil.
Here is an another way to handle above with NSTimer..
-(void)imageTapped:(UITapGestureRecognizer*)recognizer
{
CustomPopUpView *lCustomPopUpView = [[CustomPopUpView alloc]init];
//Added your imageview to Custom UIView class
[self.window addSubview:lCustomPopUpView];
[self.window bringSubviewToFront:lCustomPopUpView];
mPopupTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(closePopUp) userInfo:Nil repeats:FALSE];
}
- (void)closePopUp{
if (mPopupTimer != nil) {
[mPopupTimer invalidate];
mPopupTimer = nil;
}
for (UIView *lView in self.window.subviews) {
if ([lView isKindOfClass:[CustomPopUpView class]]) {
[lView removeFromSuperview];
}
}
}
Its better to have view behind the UIImageView to handle the TapGesture.
But here is the fix for you code:
Add this to viewDidLoad method to setup you image view to handle tap gesture
//By default the UserInteraction is disabled in UIImageView
[self.testImageView setUserInteractionEnabled:YES];
UITapGestureRecognizer *tapgesture=[[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(ShowImage:)];
tapgesture.numberOfTapsRequired=1;
tapgesture.numberOfTouchesRequired=1;
[self.testImageView addGestureRecognizer:tapgesture];
here is the method handle the gesture event
-(void)ShowImage:(UIGestureRecognizer*)recognizer{
recognizer.view.alpha=0.0;
((UIImageView*)recognizer.view).image = [UIImage imageNamed:#"clone.jpg"];
[UIView animateWithDuration:1.0 delay:0 options:0 animations:^{
recognizer.view.alpha=1.0;
} completion:^(BOOL finished) {
//[self performSelector:#selector(hideImage:) withObject:recognizer.view afterDelay:10];
[UIView animateWithDuration:1.0 delay:3.0 options:0 animations:^{
recognizer.view.alpha=0.0;
} completion:^(BOOL finished) {
((UIImageView*)recognizer.view).image=nil;
((UIImageView*)recognizer.view).alpha=1.0;
}];
}];
}
If you set the alpha of view to 0, then your view will not receive any touch event further. So its best practice to set it again to 1.0 after removed the image from UIImageView.
You may want to consider using a UIButton. Detecting touches with these is easy - as is changing their image.
You could also subclass UIControl (see http://www.raywenderlich.com/36288/how-to-make-a-custom-control).
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];
}];
}
I'm using the following code to zoom/enlarge an image from within a table view. I would like it to enlarge out to full screen. the current problem is that when it enlarges it enlarges only within the table cell (and as a result is partially hidden beneath the next table cell) instead of the full view.
-(void)zoomPhotoMethod :(id) sender
{
UITapGestureRecognizer *gesture = (UITapGestureRecognizer *) sender;
NSLog(#"Tag = %d", gesture.view.tag);
userSubmittedImageView = (UIImageView *)gesture.view;
if (!isFullScreen) {
[UIView animateWithDuration:0.5 delay:0 options:0 animations:^{
//save previous frame
prevFrame = userSubmittedImageView.frame;
UIView *popupImageViewForTableCell = [[UIView alloc] initWithFrame: CGRectMake ( 0, 0, 320, 500)];
[userSubmittedImageView setFrame:[popupImageViewForTableCell bounds]];
}completion:^(BOOL finished){
isFullScreen = TRUE;
}];
return;
}
else{
[UIView animateWithDuration:0.5 delay:0 options:0 animations:^{
[userSubmittedImageView setFrame:prevFrame];
}completion:^(BOOL finished){
isFullScreen = FALSE;;
}];
return;
}
}
updated code:
-(void)zoomPhotoMethod :(id) sender
{
UITapGestureRecognizer *gesture = (UITapGestureRecognizer *) sender;
NSLog(#"Tag = %d", gesture.view.tag);
userSubmittedImageView = (UIImageView *)gesture.view;
if (!isFullScreen) {
[UIView animateWithDuration:0.3 delay:0 options:0 animations:^{
//save previous frame
prevFrame = userSubmittedImageView.frame;
newView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)];
[self.view addSubview:newView];
UIView *popupImageViewForTableCell = [[UIView alloc] initWithFrame: CGRectMake ( 0, 0, 320, 500)];
[userSubmittedImageView setFrame:[popupImageViewForTableCell bounds]];
[newView addSubview:userSubmittedImageView];
[userSubmittedImageView setFrame:[newView bounds]];
}completion:^(BOOL finished){
isFullScreen = TRUE;
}];
return;
}
else{
[UIView animateWithDuration:0.3 delay:0 options:0 animations:^{
[userSubmittedImageView setFrame:prevFrame];
[newView setFrame:prevFrame];
}completion:^(BOOL finished){
isFullScreen = FALSE;;
}];
return;
}
}
What's happening is that you are still a subview of the tableview cell, so it's not going to be able to go outside of those bounds without being clipped.
To do what you want to do, you're going to have to:
Create a new UIImageView outside of your table view (add it as a subview outside of the tableview).
Set the image in the image view to the same image that you have in the tableview cell.
Start it at the same size and position as your tableview cell image.
Animate it up to where you want it.
When it's time to remove it, move it back down to the original tableview cell image.
Discard the imageview.
One way would be to use a copy of the image and place it outside the cell just above your original and animate this second image to full screen.
Another way would be to increase the tableView rowHeight and reload the section.
I wrote a custom UITableViewCell - image view, button and three labels now I am trying to add some animations to it. So once I tap the button, it fades away and the spinner replaces the button. After two seconds the cell is overlaid with a red color, as a subview of cell fades in, then the indicator is removed and and red overlay starts fading back out. As well the button I previously removed fades back in.
(I could not phrase it a better way :P )
The method is :
-(void)rentButtonPressed:(id)sender
{
UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
[indicator startAnimating];
indicator.center = self.rentButton.center;
[UIView animateWithDuration:0.2
animations:^{self.rentButton.alpha = 0.0;}
completion:^(BOOL finished){
[self.rentButton removeFromSuperview];
[self addSubview:indicator];
}
];
UIView *overlay = [[UIView alloc] initWithFrame:self.backgroundImage.frame];
overlay.alpha = 0.0;
overlay.backgroundColor = [UIColor redColor];
[self.contentView addSubview:overlay];
[UIView animateWithDuration:0.4
delay:2.0
options:UIViewAnimationCurveEaseInOut
animations:^{
[indicator removeFromSuperview];
overlay.alpha = 0.4;
}
completion:^(BOOL finished){
[UIView animateWithDuration:0.4
animations:^{ overlay.alpha = 0.0; }
completion:^(BOOL finished)
{
[overlay removeFromSuperview];
}
];
[self.contentView addSubview:self.rentButton];
[UIView animateWithDuration:0.4 animations:^{ self.rentButton.alpha = 1.0;}];
[self.delegate didTryToRentMovieAtCell:self];
}
];
}
So the code does fade out the button, replace it with spinner and fades in the red overlay. The problem is, the red overlay does not fade away, but disappears same with the button, instead of fading in, it just appears.
During your animation, you are changing the view hierarchy by adding and removing subviews. The UIView class method animateWithDuration:animations:completion is intended only animating property changes in a view, and not for changing the view hierarchy.
Try using the UIView class method transitionWithView:duration:options:animations:completion: instead, and use the cell's content view as the "container."
This documentation is helpful in distinguishing between animating view property changes and animating view transitions, specifically the section "Changing the Subviews of a View":
http://developer.apple.com/library/ios/#documentation/windowsviews/conceptual/viewpg_iphoneos/animatingviews/animatingviews.html