Show partial content of image using animation - ios

I am working with images in iOS. So when a user presses let's say a button, I want an image to appear to fill up from the bottom to the top.
In order be more precise, imagine a thermometer that is empty and when you press a button the thermometer fills up from the bottom to the top. But I want this with animation.
Do you have any idea how to implement that?
Thanks!

You can easily do it by having a second view acting as a mask over the image view with your image. Then you can apply simple scale transform and animate it revealing the image. Alternatively, you can animate the frame change. Effect will be the same. I'm attaching code for 2 ways of doind this.
Here is what I achieved in both cases:
Code I used for the case I:
#interface MyViewController ()
#property(nonatomic, strong) UIImageView *imageView;
#property(nonatomic, strong) UIView *maskView;
#end
#implementation MyViewController
- (void)viewDidLoad
{
[super viewDidLoad];
_imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Image"]];
_imageView.center = self.view.center;
_imageView.layer.anchorPoint = CGPointMake(0.5, 0.0);
[self.view addSubview:_imageView];
_maskView = [[UIView alloc] initWithFrame:_imageView.frame];
_maskView.backgroundColor = [UIColor whiteColor];
_maskView.center = self.view.center;
_maskView.layer.anchorPoint = CGPointMake(0.5, 0.0);
[self.view addSubview:_maskView];
}
- (IBAction)buttonTapped:(id)sender
{
[UIView animateWithDuration:1.0f
animations:^
{
_maskView.transform = CGAffineTransformMakeScale(1.0, 0.0001);
}];
}
#end
Code I used for the case II:
#interface MyViewController ()
#property(nonatomic, strong) UIImageView *imageView;
#property(nonatomic, assign) CGFloat height;
#end
#implementation MyViewController
- (void)viewDidLoad
{
[super viewDidLoad];
_imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Image"]];
_imageView.center = self.view.center;
_imageView.contentMode = UIViewContentModeBottom;
_height = CGRectGetHeight(_imageView.bounds);
CGRect frame = _imageView.frame;
frame.size.height = 0.00001;
frame.origin.y += _height;
_imageView.frame = frame;
_imageView.clipsToBounds = YES;
[self.view addSubview:_imageView];
}
- (IBAction)buttonTapped:(id)sender
{
[UIView animateWithDuration:1.0f
animations:^
{
CGRect frame = _imageView.frame;
frame.size.height = _height;
frame.origin.y -= _height;
_imageView.frame = frame;
}];
}
#end
In both cases the end effect was the same.

You can do this by changing the frame of the view within the animate block. For example, if myView was a 100x100 sized view:
myView.frame = CGRectMake(0, [UIScreen mainScreen].bounds.size.height, 0, 0);
[UIView animateWithDuration:1.0f
animations:^
{
myView.frame = CGRectMake(0, [UIScreen mainScreen].bounds.size.height, 100, 100);
}];
This example should enlarge a 100x100 view in the bottom left corner of the screen, making it appear to reveal itself from the bottom up.

Related

Cannot pan UIScrollView after zooming

I've programatically created a UIScrollView using the following code:
.h:
#interface MainController : UIViewController <UIScrollViewDelegate>
#property (nonatomic, retain) UIScrollView *mainScrollView;
#property (nonatomic, retain) UIView *mainView;
.m:
- (void) initVariables
{
// Setters
screenRect = [[UIScreen mainScreen] bounds];
screenWidth = screenRect.size.width;
screenHeight = screenRect.size.height;
// Alloc-init methods
mainScrollView = [[UIScrollView alloc] init];
mainView = [[UIView alloc] init];
}
- (void) setUpScrollView
{
mainScrollView.frame = CGRectMake(0.0f, 0.0f, screenWidth, screenHeight);
mainScrollView.contentSize = CGSizeMake(screenWidth*2, screenHeight*2);
mainScrollView.minimumZoomScale = 1.0;
mainScrollView.maximumZoomScale = 4.0;
mainScrollView.backgroundColor = [UIColor blueColor];
mainScrollView.scrollEnabled = YES;
mainScrollView.delegate = self;
mainView.frame = CGRectMake((screenWidth/2) - 25.0f, (screenHeight/2) - 25.0f, 50.0f, 50.0f);
UIImageView *testImageView = [[UIImageView alloc] init];
testImageView.backgroundColor = [UIColor redColor];
testImageView.frame = CGRectMake(0.0f, 0.0f, 50.0f, 50.0f);
// Add subviews
[self.view addSubview:mainScrollView];
[mainScrollView addSubview:mainView];
[mainView addSubview:testImageView];
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return mainView;
}
The zoom works well now. However, I can only pan when the UIScrollView is at the minimum zoom. All other times, when I pinch-zoom in, when I release my fingers the UIView shifts slightly off-centre, despite being centred when I was zooming, and I can no longer pan the UIScrollView.
Also, after zooming in, if I zoom back out again to minimum zoom, I still cannot pan.
TL;DR - In other words, UIScrollView's pan only before using the pinch zoom feature, after which it breaks.
How can I prevent this from happening, and allow the UIScrollView's pan to always be enabled? All help appreciated.
Try to add another view in scrollView with same size of screen and then add mainView and imageView. It may solve your proble. :)

How to make a image loop across the screen [horizontally]

This is what I got but I am getting errors as you see in the picture.
I have the IBOutlets for UIImageView *bottomGroundContinue and one for *bottomGround
#implementation viewController
#property (nonatomic, assign) BOOL keepGoing;
#property (nonatomic, strong) UIImageView *bottomGround;
#property (nonatomic, strong) UIImageView *bottomGroundContinue;
- (void)getReady {
UIImage *theImage = [UIImage imageNamed:#"bottomGround.png"];
self.bottomGround = [[UIImageView alloc] initWithImage:theImage];
self.bottomGroundContinue = [[UIImageView alloc] initWithImage:theImage];
self.bottomGround.frame = self.view.bounds;
self.bottomGRoundContinue.frame = CGRectOffset(self.view.bounds, self.bottomGround.bounds.size.width, 0.0);
[self.view addSubview:self.bottomGround];
[self.view addSubview:self.bottomGroundContinue];
self.keepGoing = YES;
}
- (void)go {
CGFloat width = self.bottomGround.bounds.size.width;
[UIView animateWithDuration:1.0 delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
self.bottomGround.frame = CGRectOffset(self.bottomGround.frame, -width, 0);
self.bottomGroundContinue.frame = CGRectOffset(self.bottomGroundContinue.frame, -width, 0);
} completion:^(BOOL finished) {
if (self.keepGoing) {
// now B is where A began, so swap them and reposition B
UIImageView *temp = self.bottomGround;
self.bottomGround = self.bottomGroundContinue;
self.bottomGroundContinue = temp;
self.bottomGroundContinue.frame = CGRectOffset(self.view.bounds, self.view.bounds.size.width, 0.0);
// recursive call, but we don't want to wind up the stack
[self performSelector:#selector(go) withObject:nil afterDelay:0.0];
}
}];
}
You are declaring properties inside of #implementation. You need to declare them inside of #interface.

Why are the dimensions of my modal view different from what I expect?

I created this dialog in IB:
I set a segue the following way:
This is how I configured the ViewController:
And this is how I configured the view metrics:
Here's what I get in the iOS Simulator:
So: could somebody tell me why, how could I get this they way I visually intend to and where I could find a very good Interface builder tutorial?
Thanks in advance.
The "Form Sheet" modal presentation style always uses the same size container, and you can't override it. With iOS 7 or later, you can define a custom modal presentation style to accomplish what you want, or search GitHub.
Here's an iOS 6-compatible implementation with a custom size.
/** Created in answer to mirx's question at http://stackoverflow.com/questions/18803961/why-are-the-dimensions-of-my-modal-view-different-from-what-i-expect/18805374?noredirect=1#comment27741546_18805374 */
#interface AHPresentingViewController : UIViewController
#property (nonatomic, weak) UIView *presentedBackgroundView;
#property (nonatomic, weak) UIViewController *formController;
#end
#implementation AHPresentingViewController
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
UITableViewController *table = [UITableViewController new];
table.title = #"Form";
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:table];
table.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(dismissPresentedForm:)];
[self presentForm:nav size:CGSizeMake(320, 240)];
}
-(void)presentForm:(UIViewController *)formVC size:(CGSize)size {
if(self.formController) return;
NSLog(#"Form view: %#",formVC.view);
UIView *background;
self.presentedBackgroundView = background = [[UIView alloc] initWithFrame:self.view.bounds];
background.opaque = NO;
background.backgroundColor = [UIColor blackColor];
background.alpha = 0;
[self.view addSubview:background];
formVC.view.frame = (CGRect){CGPointZero, size};
formVC.view.center = CGPointMake(CGRectGetWidth(self.view.bounds) / 2., CGRectGetHeight(self.view.bounds) + CGRectGetHeight(formVC.view.bounds));
[self addChildViewController:formVC];
self.formController = formVC;
[self.view addSubview:formVC.view];
[UIView animateWithDuration:0.5 animations:^{
background.alpha = 0.4;
formVC.view.center = CGPointMake(self.view.bounds.size.width / 2., self.view.bounds.size.height / 2.);
}];
}
-(void)dismissPresentedForm:(id)sender {
if(!self.formController) return;
[UIView animateWithDuration:0.5 animations:^{
self.presentedBackgroundView.alpha = 0;
self.formController.view.center = CGPointMake(CGRectGetWidth(self.view.bounds) / 2., CGRectGetHeight(self.view.bounds) + CGRectGetHeight(self.formController.view.bounds));
} completion:^(BOOL finished) {
[self.presentedBackgroundView removeFromSuperview];
[self.formController.view removeFromSuperview];
[self.formController removeFromParentViewController];
}];
}
#end

UITapGestureRecognizer not responding on animated subview

I have a simple program that creates a subview and animates it across the screen.
As part of this program, I would like to add functionality when the subview is tapped. I am using the following method to create the subview, add the UITapGestureRecognizer and then animate the subview:
int randomName = arc4random() % ([pieceNames count] - 1);
int animationDuration = arc4random() % 5 + 5 ;
NSString *randomPiece = [pieceNames objectAtIndex:randomName];
float yStart = arc4random() % 650;
float yEnd = arc4random() % 650;
UIView *piece = [[PieceView alloc]initWithFrame:CGRectMake(100.0, yStart, 75.0, 75.0)];
[piece setValue:randomPiece forKey:#"name"];
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc]initWithTarget:self
action:#selector(handleTouch:)];
[piece addGestureRecognizer:recognizer];
[[self view] addSubview:piece];
[UIView animateWithDuration:animationDuration
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^(void){
piece.center = CGPointMake(950.0, yEnd);
} completion:^(BOOL done){
[piece removeFromSuperview];
}];
Here is the code that handles the tap:
PieceView *pv = (PieceView *) recognizer.view;
NSLog(#"%# was tapped", pv.name);
What happens is when a PieceView is touched the program does not respond. However, if I remove the animation block then the program responds to the tap.
Why does the UITapGestureRecognizer fail to respond to PieceView when it is animated?
I struggled with this same problem, and it boils down to this: the animated view only ever is in two places: the starting position, and the ending position. Core Animation simply renders the view's layer in interpolated positions between the start and end points over a period of time.
It's almost like when you look to the stars and realize that what you see is not actually what is happening right now. :)
Luckily, the solution is pretty simple. You can put a tap recognizer on the superview and then inspect your animated view's presentationLayer (which does give you an accurate frame at any point in time) to determine if your tap is a hit or not.
I've built a simple UIViewController that demonstrates both the problem and solution:
#import <UIKit/UIKit.h>
#interface MSMViewController : UIViewController
#end
And the implementation:
#import "MSMViewController.h"
#interface MSMViewController ()
#property (nonatomic, strong) UIView *animatedView;
#end
#implementation MSMViewController
- (void)viewDidLoad {
[super viewDidLoad];
CGRect startFrame = CGRectMake(125, 0, 70, 70);
CGRect endFrame = CGRectMake(125, 400, 70, 70);
// draw a box to show where the animated view begins
UIView *startOutlineView = [[UIView alloc] initWithFrame:startFrame];
startOutlineView.layer.borderColor = [UIColor blueColor].CGColor;
startOutlineView.layer.borderWidth = 1;
[self.view addSubview:startOutlineView];
// draw a box to show where the animated view ends
UIView *endOutlineView = [[UIView alloc] initWithFrame:endFrame];
endOutlineView.layer.borderColor = [UIColor blueColor].CGColor;
endOutlineView.layer.borderWidth = 1;
[self.view addSubview:endOutlineView];
self.animatedView = [[UIView alloc] initWithFrame:startFrame];
self.animatedView.backgroundColor = [UIColor yellowColor];
[self.view addSubview:self.animatedView];
[UIView animateWithDuration:10 delay:2 options:UIViewAnimationOptionAllowUserInteraction animations:^{
self.animatedView.frame = endFrame;
} completion:nil];
// this gesture recognizer will only work in the space where endOutlintView is
UITapGestureRecognizer *boxTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(boxTap:)];
[self.animatedView addGestureRecognizer:boxTap];
// this one will work
UITapGestureRecognizer *superviewTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(superviewTap:)];
[self.view addGestureRecognizer:superviewTap];
}
- (void)boxTap:(UITapGestureRecognizer *)tap {
NSLog(#"tap. view is at %#", NSStringFromCGPoint(self.animatedView.frame.origin));
}
- (void)superviewTap:(UITapGestureRecognizer *)tap {
CGRect boxFrame = [self.animatedView.layer.presentationLayer frame];
if (CGRectContainsPoint(boxFrame, [tap locationInView:self.view])) {
NSLog(#"we tapped the box!");
}
}
#end
The solution is much simpler, you just need to set the animation's option UIViewAnimationOptions.allowUserInteraction.
UIView.animate(withDuration: duration, delay: 0.1, options: [.allowUserInteraction], animations: {
...
}, completion: { (completed) in
...
}

Why are animations on bounds of an UILabel only working when increasing the size?

I noticed that when i change the bounds of an UILabel in an animation block it only works if i increase the size, when i decrease the size the UILabel just changes his size but doesn't animate.
Replacing the UILabel with a plain UIView works as intended.
Note: Changing the contentMode property of the UILabel to UIViewContentModeScaleToFill fixes this issue, but i still don't understand why it works when increasing the size without changing the contentMode property.
#import "FooView.h"
#interface FooView ()
#property (strong, nonatomic) UILabel *label;
#property (assign, nonatomic) BOOL resized;
#end
#implementation FooView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor lightGrayColor];
self.label = [[UILabel alloc] initWithFrame:(CGRect){0, 0, frame.size}];
self.label.backgroundColor = [UIColor greenColor];
[self addSubview:self.label];
_resized = false;
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(changeSize)];
tapRecognizer.numberOfTapsRequired = 1;
[self addGestureRecognizer:tapRecognizer];
}
return self;
}
- (void)changeSize {
[UIView animateWithDuration:0.8
delay:0.0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
CGRect bounds = self.label.bounds;
if (self.resized) {
bounds.size.height *= 2;
bounds.size.width *= 2;
} else {
bounds.size.width /= 2;
bounds.size.height /= 2;
}
self.label.bounds = bounds;
}
completion:^(BOOL finished) {
self.resized = !self.resized;
}];
}
#end
It's because UILabel sets its layer's contentsGravity to the direction text is being rendered, which happens to default to UIViewContentModeLeft (or #"left"). Thus, when the layer is told to animate, it first takes a glance at its contents gravity and bases subsequent animations on that. Because it sees #"left" where there should be #"resize", it assumes that the scaling animation should begin from the left, but it also has to respect the constraints you've given it (the bounds changes), so your label appears to jump into its final size then settle where it should in the center of the screen.
If you want to leave contentMode alone, use CATransform3D's and scale the label's layer that way instead of a bounds change.

Resources