Replicating iBooks zoom/fade animation - ios

I'm trying to replicate the zooming in and fading of a book animation that happens on the iPad. Basically what happens if you haven't seen is a book is on a shelf, when tapped, it'll grow towards the center of the screen. When it nears the center of the screen, it cross fades with the page of the book you are on, whether that be the cover or page 100. On the iPhone, the cover of the book grows towards the center of the screen, then halfway through the transition, it fades into the last page you were on in the book, and the zoom finishes taking up the whole screen.
What I tried to do in order to replicate this is, someone clicks on my collectionView. I get the frame for that cell and take a screenshot of that cell. I create a layer with the contents being that image.
CALayer *smallLayer = [CALayer layer];
UIImage *smallImage = [UIImage renderImageFromView:self.smallZoomView];
smallLayer.frame = self.smallZoomRect;
smallLayer.contents = (__bridge id)smallImage.CGImage;
I add it to the container view that I am using for the animation. For the animation part, I tried this:
[CATransaction begin];
[CATransaction setAnimationDuration:5.0];
[CATransaction setDisableActions:YES];
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
anim.fromValue = [NSNumber numberWithFloat:1.0];
anim.toValue = [NSNumber numberWithFloat:2.0];
[self.smallZoomLayer addAnimation:anim forKey:#"Zoom"];
My first question is, how do I make it grow towards the center of the view regardless of where the animation starts from. Since the user is clicking on a collection view cell, it currently just grows from the center of that cell versus growing towards the center of the view.
My second question is how to cross fade with the next screen that I am going to show. I thought I could do something like this: How to crossfade between 2 images on iPhone using Core Animation to get the cross fading, but I do not know how to take a screenshot of a view that is not currently being shown yet.
Any thoughts? If this approach is wrong, let me know! Thanks.

Here's another way that I worked on. It doesn't use a snapshot, but instead scales the actual view of the PageViewController, adds it as a subview underneath the coverView, in containerView, and then does a similar animation. I don't know if this is any better from a "good coding practices" standpoint, but it just shows another way to go about it (I've been teaching myself this stuff, so I'm trying out different alternatives).
#interface ViewController ()
#property (strong,nonatomic) UIView *pageView;
#property (strong,nonatomic) PageController *pageVC;
#end
#implementation ViewController
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.pageVC = [self.storyboard instantiateViewControllerWithIdentifier:#"Page"]; // controller with the page contnent
self.pageView = self.pageVC.view;
self.pageView.transform = CGAffineTransformMakeScale(self.coverView.frame.size.width/self.pageView.frame.size.width, self.coverView.frame.size.height/self.pageView.frame.size.height);
[self.containerView insertSubview:self.pageView belowSubview:self.coverView];
[self.containerView constrainViewEqual:self.pageView];
}
-(IBAction)openCover:(id)sender {
self.coverView.layer.anchorPoint = CGPointMake(0, .5);
self.coverView.center = CGPointMake(self.coverView.center.x - self.coverView.frame.size.width/2, self.coverView.center.y);
[self.containerView removeConstraints:self.containerView.constraints];
[self.view removeConstraints:self.view.constraints];
[self.view constrainViewEqual:self.containerView];
[self.containerView constrainViewLeft:self.coverView];
[self.containerView constrainViewEqual:self.pageView];
NSInteger animationTime = 1;
[UIView animateWithDuration:animationTime animations:^{
self.pageView.transform = CGAffineTransformIdentity;
[self.view layoutIfNeeded];
}];
[UIView animateWithDuration:animationTime animations:^{
CATransform3D transform = CATransform3DIdentity;
transform =CATransform3DMakeRotation(M_PI_2, 0.0, -1.0, 0.0);
transform.m34 = 1/1000.0;
transform.m14 = -2/10000.0;
self.coverView.layer.transform =transform;
} completion:^(BOOL finished) {
[self.view.window addSubview:self.pageView];
[self.view.window setRootViewController:self.pageVC];
}];
}

Here is my attempt to recreate the iBooks open cover animation. It looks quite good I think. ViewController is the initial controller that has an image view (imageView) inside a UIView (pageView) of the same size, which is 90 x 122 in my example. imageView has an image of a book cover, and I add a subview underneath imageView of a snapshot of the other controller's view (FirstPageController) -- I had a question about making these snapshots, and I saw you posted one too. I solved it by what seems like a hack -- I added FirstPageController's view as a subview of my view, did the snapshotting, and then removed it from my view. Hopefully, someone will provide a better way to do this. I did some of the sizing and centering using NSLayoutConstraints, and I have a category that I use in other projects to keep that code separated out. Here is the ViewController.m code:
#import "ViewController.h"
#import "UIView+RDConstraintsAdditions.h"
#import <QuartzCore/QuartzCore.h>
#import "FirstPageController.h"
#interface ViewController ()
#property (strong,nonatomic) FirstPageController *fpController;
#end
#implementation ViewController
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.fpController = [self.storyboard instantiateViewControllerWithIdentifier:#"FirstPage"];
[self.view insertSubview:self.fpController.view belowSubview:self.view];
UIGraphicsBeginImageContext(self.fpController.view.bounds.size);
[self.fpController.view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.fpController.view removeFromSuperview];
self.imageView2 = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 90, 122)];
self.imageView2.image = viewImage;
[self.pageView insertSubview:self.imageView2 belowSubview:self.imageView];
}
-(IBAction)expandImageView:(id)sender {
self.imageView.layer.anchorPoint = CGPointMake(0, .5);
self.imageView.center = CGPointMake(self.imageView.center.x - self.imageView.frame.size.width/2, self.imageView.center.y);
[self.view removeConstraints:self.view.constraints];
[self.pageView removeConstraints:self.pageView.constraints];
[self.view constrainViewEqual:self.pageView];
[self.pageView constrainViewLeft:self.imageView];
[self.pageView constrainViewEqual:self.imageView2];
[UIView animateWithDuration:2 animations:^{
[self.view layoutIfNeeded];
}];
[UIView animateWithDuration:2 animations:^{
CATransform3D transform = CATransform3DIdentity;
transform =CATransform3DMakeRotation(M_PI_2, 0.0, -1.0, 0.0);
transform.m34 = 1/1000.0;
transform.m14 = -2/10000.0;
self.imageView.layer.transform =transform;
} completion:^(BOOL finished) {
[self.view.window setRootViewController:self.fpController];
}];
}
Here is the category on UIView that I used:
#import "UIView+RDConstraintsAdditions.h"
#implementation UIView (RDConstraintsAdditions)
-(void)constrainViewEqual:(UIView *) view {
[view setTranslatesAutoresizingMaskIntoConstraints:NO];
NSLayoutConstraint *con1 = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeCenterX relatedBy:0 toItem:view attribute:NSLayoutAttributeCenterX multiplier:1 constant:0];
NSLayoutConstraint *con2 = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeCenterY relatedBy:0 toItem:view attribute:NSLayoutAttributeCenterY multiplier:1 constant:0];
NSLayoutConstraint *con3 = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeWidth relatedBy:0 toItem:view attribute:NSLayoutAttributeWidth multiplier:1 constant:0];
NSLayoutConstraint *con4 = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeHeight relatedBy:0 toItem:view attribute:NSLayoutAttributeHeight multiplier:1 constant:0];
NSArray *constraints = #[con1,con2,con3,con4];
[self addConstraints:constraints];
}
-(void)constrainViewLeft:(UIView *) view {
[view setTranslatesAutoresizingMaskIntoConstraints:NO];
NSLayoutConstraint *con1 = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeLeft relatedBy:0 toItem:view attribute:NSLayoutAttributeCenterX multiplier:1 constant:0];
NSLayoutConstraint *con2 = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeCenterY relatedBy:0 toItem:view attribute:NSLayoutAttributeCenterY multiplier:1 constant:0];
NSLayoutConstraint *con3 = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeWidth relatedBy:0 toItem:view attribute:NSLayoutAttributeWidth multiplier:1 constant:0];
NSLayoutConstraint *con4 = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeHeight relatedBy:0 toItem:view attribute:NSLayoutAttributeHeight multiplier:1 constant:0];
NSArray *constraints = #[con1,con2,con3,con4];
[self addConstraints:constraints];
}
At the end of the animation, I switch out the root view controller of the window to get the "live" FirstPageController view instead of the snapshot.

Related

iOS and corona view landscape issue

I am trying to integrate corona (game platform) with iOS project using corona cards. Below is what I am trying to achieve:
Programmatically load corona view controller (ViewController.m/ViewController.h) from AppDelegate.m
Force landscape mode for corona view controller
Below is the code:
AppDelegate.m
ViewController *viewController = [[ViewController alloc] init];
self.window.rootViewController = viewController;
[self.window makeKeyAndVisible];
ViewController.m
#interface ViewController ()
#property (nonatomic, strong) CoronaViewController *coronaController;
#end
- (void)viewDidAppear:(BOOL)animated {
_coronaController = [[CoronaViewController alloc] init];
[self addChildViewController:_coronaController];
CoronaView *coronaView = (CoronaView *)_coronaController.view;
coronaView.frame = self.view.frame;
[self.view addSubview:coronaView];
[coronaView run];
}
Here's the screenshot of how it looks:
Portrait mode: image
Landscape mode: image
The issue is that when I go to landscape, the image should take up the entire screen but instead it just shows partial image.
* UPDATE *
I was able to get this working by doing the following:
CGRect appFrame = [UIScreen mainScreen].bounds;
_coronaView.frame = CGRectMake(0, -162, appFrame.size.height, appFrame.size.width);
[self.view addSubview:_coronaView];
[_coronaView setNeedsLayout];
However, I am not sure why I had to set my y to -162. Hope this helps.
This happens because when you go to Landscape mode from Portrait mode, the width and height of screen changes. So, coronaView.frame.size.width != [UIScreen mainScreen].bounds.size.width when shifted from Portrait to Landscape
To resolve these issues, it's better to use AutoLayout for setting frame or setting frame in viewDidLayoutSubviews in ViewControllers,
-(void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
CoronaView *coronaView = (CoronaView *)_coronaController.view;
coronaView.frame = self.view.frame;
}
or
- (void)viewDidAppear:(BOOL)animated {
_coronaController = [[CoronaViewController alloc] init];
[self addChildViewController:_coronaController];
CoronaView *coronaView = (CoronaView *)_coronaController.view;
[self.view addSubview:coronaView];
NSLayoutConstraint *constraint1 = [NSLayoutConstraint constraintWithItem:coronaView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1 constant:0];
NSLayoutConstraint *constraint2 = [NSLayoutConstraint constraintWithItem:coronaView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1 constant:0];
NSLayoutConstraint *constraint3 = [NSLayoutConstraint constraintWithItem:coronaView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:0];
NSLayoutConstraint *constraint4 = [NSLayoutConstraint constraintWithItem:coronaView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1 constant:0];
[self.view addConstraints:#[constraint1, constraint2, constraint3, constraint4]];
}

Custom Transition Only Works First Time on Retina Simulator

I have a custom presentation using the UIViewControllerContextTransitioning protocol that splits the view of the initial controller in half (actually an image of that view), and slides the two haves off the screen to the left and right. The animation works properly for the presentation and dismissal the first time it is carried out, but is wrong on any subsequent presentations.
Specifically, the image views that hold the half images of the view don't respect the constraints they are given, and so are twice the size they should be (they're the size dictated by the retina image). This only happens on simulators with retina displays.
The animator object is deallocated after each presentation and dismissal, so it's not at all clear why it should behave differently on subsequent runs.
Here is the code, including the logs that show the size of one of the image views both before and after the constraints are applied,
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)context {
self.ctxContainerView = [context containerView];
UIView *toView = [context viewControllerForKey:UITransitionContextToViewControllerKey].view;
UIView *fromView = [context viewControllerForKey:UITransitionContextFromViewControllerKey].view;
// Create image from view
UIView *viewToImage = (self.isPresenting)? fromView : toView;
UIGraphicsBeginImageContextWithOptions(viewToImage.bounds.size, viewToImage.opaque, 0);
[viewToImage.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage * img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// Create left and right haves of the view image
CGImageRef tmp = CGImageCreateWithImageInRect([img CGImage], CGRectMake(0, 0, img.size.width * img.scale*0.5, img.size.height * img.scale));
UIImage *imgLeft = [UIImage imageWithCGImage:tmp];
CGImageRef tmp2 = CGImageCreateWithImageInRect([img CGImage], CGRectMake(img.size.width * img.scale*0.5, 0, img.size.width * img.scale*0.5, img.size.height * img.scale));
UIImage *imgRight = [UIImage imageWithCGImage:tmp2];
CGImageRelease(tmp);
CGImageRelease(tmp2);
self.snapshotViewLeft = [[UIImageView alloc] initWithImage:imgLeft];
self.snapshotViewRight = [[UIImageView alloc] initWithImage:imgRight];
NSLog(#"Before constraints %#", NSStringFromCGSize(self.snapshotViewLeft.frame.size)); //prints {375, 1334} all the time
if (self.isPresenting) {
[self.ctxContainerView insertSubview:toView belowSubview:fromView];
[self.ctxContainerView addSubview:self.snapshotViewLeft];
[self.ctxContainerView addConstraints: [self setupConstraintsForLeftView:self.snapshotViewLeft]];
[self.ctxContainerView addSubview:self.snapshotViewRight];
[self.ctxContainerView addConstraints: [self setupConstraintsForRightView:self.snapshotViewRight]];
[self.ctxContainerView layoutIfNeeded];
NSLog(#"After constraints %#", NSStringFromCGSize(self.snapshotViewLeft.frame.size)); // prints {187.5, 667} the first time (which is the correct size), but {375, 1334} the second time the controller is presented
// Remove the source view, and animate out the two image views
[fromView removeFromSuperview];
self.leftCon.constant = -self.ctxContainerView.frame.size.width/2;
self.rightCon.constant = self.ctxContainerView.frame.size.width/2;
[UIView animateWithDuration:ANIMATION_TIME animations:^{
[self.ctxContainerView layoutIfNeeded];
} completion:^(BOOL finished) {
[self.snapshotViewRight removeFromSuperview];
[self.snapshotViewLeft removeFromSuperview];
[context completeTransition:YES];
}];
}else{
[self.ctxContainerView addSubview:self.snapshotViewLeft];
[self.ctxContainerView addConstraints: [self setupConstraintsForLeftView:self.snapshotViewLeft]];
self.leftCon.constant = -self.ctxContainerView.frame.size.width/2;
[self.ctxContainerView addSubview:self.snapshotViewRight];
[self.ctxContainerView addConstraints: [self setupConstraintsForRightView:self.snapshotViewRight]];
self.rightCon.constant = self.ctxContainerView.frame.size.width/2;
[self.ctxContainerView layoutIfNeeded];
// Animate in the two image views
self.leftCon.constant = 0;
self.rightCon.constant = 0;
[UIView animateWithDuration:ANIMATION_TIME animations:^{
[self.ctxContainerView layoutIfNeeded];
} completion:^(BOOL finished) {
[self.snapshotViewRight removeFromSuperview];
[self.snapshotViewLeft removeFromSuperview];
[fromView removeFromSuperview];
[context completeTransition:YES];
}];
}
}
-(NSArray *)setupConstraintsForLeftView:(UIView *) inView {
inView.translatesAutoresizingMaskIntoConstraints = NO;
self.leftCon = [NSLayoutConstraint constraintWithItem:inView attribute:NSLayoutAttributeLeft relatedBy:0 toItem:self.ctxContainerView attribute:NSLayoutAttributeLeft multiplier:1 constant:0];
NSLayoutConstraint *con1 = [NSLayoutConstraint constraintWithItem:inView attribute:NSLayoutAttributeBottom relatedBy:0 toItem:self.ctxContainerView attribute:NSLayoutAttributeBottom multiplier:1 constant:0];
NSLayoutConstraint *con2 = [NSLayoutConstraint constraintWithItem:inView attribute:NSLayoutAttributeTop relatedBy:0 toItem:self.ctxContainerView attribute:NSLayoutAttributeTop multiplier:1 constant:0];
NSLayoutConstraint *con3 = [NSLayoutConstraint constraintWithItem:inView attribute:NSLayoutAttributeWidth relatedBy:0 toItem:self.ctxContainerView attribute:NSLayoutAttributeWidth multiplier:0.5 constant:0];
NSArray *arr = #[self.leftCon,con1,con2,con3];
return arr;
}
-(NSArray *)setupConstraintsForRightView:(UIView *) inView {
inView.translatesAutoresizingMaskIntoConstraints = NO;
self.rightCon = [NSLayoutConstraint constraintWithItem:inView attribute:NSLayoutAttributeRight relatedBy:0 toItem:self.ctxContainerView attribute:NSLayoutAttributeRight multiplier:1 constant:0];
NSLayoutConstraint *con1 = [NSLayoutConstraint constraintWithItem:inView attribute:NSLayoutAttributeBottom relatedBy:0 toItem:self.ctxContainerView attribute:NSLayoutAttributeBottom multiplier:1 constant:0];
NSLayoutConstraint *con2 = [NSLayoutConstraint constraintWithItem:inView attribute:NSLayoutAttributeTop relatedBy:0 toItem:self.ctxContainerView attribute:NSLayoutAttributeTop multiplier:1 constant:0];
NSLayoutConstraint *con3 = [NSLayoutConstraint constraintWithItem:inView attribute:NSLayoutAttributeWidth relatedBy:0 toItem:self.ctxContainerView attribute:NSLayoutAttributeWidth multiplier:0.5 constant:0];
NSArray *arr = #[self.rightCon,con1,con2,con3];
return arr;
}
The question is, why is the behavior different for runs after the first, even though the system should be back to the same start state after the first presentation and dismissal?

iOS - Performing several simultaneous animations with autolayout

I want to perform two simultaneous movement animations. First animation on firstView starts immediately. Second animation on secondView, starts after a slight delay while the first animation is still running. secondView constraint is related to firstView. The code works perfectly well on iOS 8.
firstView and secondView are subviews of view
view
|--- firstView
|--- secondView
Code:
UIView *firstView = ...;
UIView *secondView = ...;
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:firstView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.topLayoutGuide attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0];
[self.view addConstraint:constraint];
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
}];
[UIView animateWithDuration:0.5 delay:0.15 options:UIViewAnimationOptionCurveEaseInOut animations:^{
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:secondView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:firstView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0];
[self.view addConstraint:constraint];
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
}];
On iOS 7, once the second layoutIfNeeded is called, the first animations stops and only the seconds animation animates.
Any suggestions?
Answering my own question.
I ended up doing the animation in a 2-step (3-step, depends how you count) solution. First add the constraints without calling layoutIfNeeded. Then update the firtView and secondView frames. Lastly call layoutIfNeeded in the completion block of the last animation.
Code:
UIView *firstView = ...;
UIView *secondView = ...;
/* first step */
NSLayoutConstraint *constraint1 = [NSLayoutConstraint constraintWithItem:firstView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.topLayoutGuide attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0];
[self.view addConstraint:constraint1];
NSLayoutConstraint *constraint2 = [NSLayoutConstraint constraintWithItem:secondView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:firstView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0];
[self.view addConstraint:constraint2];
/* second step */
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
CGRect firstViewFrame = CGRectMake(...); // set frame according to constaint1
firstView.frame = firstViewFrame;
}];
[UIView animateWithDuration:0.5 delay:0.15 options:UIViewAnimationOptionCurveEaseInOut animations:^{
CGRect secondViewFrame = CGRectMake(...); // set frame according to constaint2
secondView.frame = secondViewFrame;
} completion:^(BOOL finished) {
/* second and a half step */
[self.view setNeedsLayout];
[self.view layoutIfNeeded];
}];
Two important notes:
You need to call layoutIfNeeded within the animation block. Apple
actually recommends you call it once before the animation block to
ensure that all pending layout operations have been completed.
You need to call it specifically on the parent view (e.g.
self.view), not the child view that has the constraints attached to
it. Doing so will update all constrained views, including animating
other views that might be constrained to the view that you changed
the constraint of (e.g. View B is attached to the bottom of View A
and you just changed View A's top offset and you want View B to
animate with it).
You must do it like this:
[self.view layoutIfNeeded];
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:firstView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.topLayoutGuide attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0];
[self.view addConstraint:constraint];
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
}];

Create Square Rather Than Rounded Edge Effect UIProgressView

I have a UIProgressView that I am trying to make have square rather than rounded edges. I don't see any obvious way to do this, so my thought is to make the progress image itself a bit larger than its square frame, so that the progress image is thereby cropped into a square shape. However, no matter how I alter the size of the progress image, I can't seem to accomplish this effect. Can anyone tell me if there is a way to achieve what I am going for?
Here is what I currently have inside a UIView subclass:
self.progressView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
self.progressView.trackImage = [PLStyle imageForColor:[PLColors greyLight] inFrame:CGRectMake(0, 0, 1, ProgressViewHeight)];
self.progressView.progressImage = [PLStyle imageForColor:[PLColors orange] inFrame:CGRectMake(0, 0, 1, ProgressViewHeight)];
[self.progressView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self addSubview:self.progressView];
[self addConstraint:
[NSLayoutConstraint constraintWithItem:self.progressView
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:self.imageView
attribute:NSLayoutAttributeTrailing
multiplier:1
constant:LeadingOrTrailingSpace]];
[self addConstraint:
[NSLayoutConstraint constraintWithItem:self.progressView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterY
multiplier:1
constant:0]];
[self addConstraint:
[NSLayoutConstraint constraintWithItem:self.progressView
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:self.counter
attribute:NSLayoutAttributeLeading
multiplier:1
constant:-LeadingOrTrailingSpace]];
[self addConstraint:
[NSLayoutConstraint constraintWithItem:self.progressView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1
constant:ProgressViewHeight]];
//the method definition used above for creating an image in the progress view
+ (UIImage *)imageForColor:(UIColor *)color inFrame:(CGRect)rect
{
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [color CGColor]);
CGContextFillRect(context, rect);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
Set UIProgressViewStyle to bar
OBJECTIVE-C:
[progressView setProgressViewStyle: UIProgressViewStyleBar];
Swift:
progressView.progressViewStyle = .bar
You could always roll your own pretty easily.
#interface MyProgressView : UIView
#property (assign) float progress;
#end
#implementation MyProgressView {
UIView *progressBar;
}
- (void)setProgress:(float)progress {
_progress = progress;
[self setNeedsLayout];
}
- (void)layoutSubviews {
CGRect frame = self.bounds;
frame.size.width *= progress;
progressBar.frame = frame;
}
#end
You could also use a "min/max value" + "set value" and do the progress calculation if that better suits your application. You could also use CALayers if you dislike smashing together a bunch of UIViews to do simple rectangle drawing.
Use progressViewStyle = .bar
let p = UIProgressView(frame: .zero)
p.progressViewStyle = .bar

Subviews Not Positioned Correctly During Animation

I have a simple animation where I'm expanding a new view from the center of the old one, while that one fades out. However, the subviews of this new view (a button and a label) "fly" in from off the lower right hand side of the screen as that new view expands to take up the whole screen. I've tried with and without autolayout turned on, and while the two scenarios give different results, they're both wrong.
The set up is simple, I have two unconnected view controllers in a storyboard, and use the following code to animate the view change:
-(void)switchViews2:(id)sender {
UIWindow *win = self.view.window;
YellowController *yellow = [self.storyboard instantiateViewControllerWithIdentifier:#"Yellow"];
yellow.view.frame = CGRectMake(0, 0, 1, 1);
yellow.view.center = self.view.center;
[win addSubview:yellow.view];
CGRect frame = self.view.frame;
[UIView animateWithDuration:5 animations:^{
yellow.view.frame = frame;
self.view.alpha = 0;
}
completion:^(BOOL finished) {
[self.view removeFromSuperview];
win.rootViewController = yellow;
}];
}
The question is, why don't the subviews stay where they're supposed to inside their superview as it animates.
To grow a subview into place, particularly when using Autolayout, it is far simpler to animate the transform property instead of the frame. This way, the subview's bounds property does not change, so it does not feel the need to relay out its subviews all the time during the animation.
Add your subview with its final frame, set its transform to be a scaling affine transform of, say, 0.1, then animate it to the identity transform. It will grow out from the center point, with all subviews in the correct position.
The problem was with the layout constraints. If instead of setting the view frame in the animation block, I add constraints between the window and the new view, then call layoutIfNeeded in the animation block it works correctly:
-(void)switchViews2:(id)sender {
UIWindow *win = self.view.window;
YellowController *yellow = [self.storyboard instantiateViewControllerWithIdentifier:#"Yellow"];
[yellow.view setTranslatesAutoresizingMaskIntoConstraints:NO];
yellow.view.frame = CGRectMake(0, 0, 1, 1);
yellow.view.center = self.view.center;
[win addSubview:yellow.view];
NSLayoutConstraint *con1 = [NSLayoutConstraint constraintWithItem:yellow.view attribute:NSLayoutAttributeLeading relatedBy:0 toItem:win attribute:NSLayoutAttributeLeading multiplier:1 constant:0];
NSLayoutConstraint *con2 = [NSLayoutConstraint constraintWithItem:yellow.view attribute:NSLayoutAttributeTop relatedBy:0 toItem:win attribute:NSLayoutAttributeTop multiplier:1 constant:20];
NSLayoutConstraint *con3 = [NSLayoutConstraint constraintWithItem:yellow.view attribute:NSLayoutAttributeTrailing relatedBy:0 toItem:win attribute:NSLayoutAttributeTrailing multiplier:1 constant:0];
NSLayoutConstraint *con4 = [NSLayoutConstraint constraintWithItem:yellow.view attribute:NSLayoutAttributeBottom relatedBy:0 toItem:win attribute:NSLayoutAttributeBottom multiplier:1 constant:0];
[win addConstraints:#[con1,con2,con3,con4]];
[UIView animateWithDuration:1 animations:^{
[win layoutIfNeeded];
self.view.alpha = 0;
}
completion:^(BOOL finished) {
[self.view removeFromSuperview];
win.rootViewController = yellow;
}];
}

Resources