Create Square Rather Than Rounded Edge Effect UIProgressView - ios

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

Related

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?

Center a dynamically created UIView over a UILabel in the storyboard not working

I have a UILabel that is placed on my Storyboard with AutoLayout. (Constraints include height, width and center horizontally, and vertical spacing from the top).
I have UIView that makes a circle with the drawRect method using a UIBeizerPath.
Nothing fancy so far.
For simplicity sake I am using hard coded numbers to help illustrate my problem.
Now I want the CircleView to be placed over the UILabel, centering the label in the circle.
However, I can not get anything to line up correctly. Its like the anchor points or something is messing up.
I tried setting the CircleView's centerpoint to the labels center point. No luck.
I tried setting the CircleViews X to the location of the label minus the width divided by two. No luck.
The closest I can get is with the Y coordinate. I use the label.center.y - 52.5 (half of the radius).
cv = [[CircleView alloc] initWithFrame:CGRectMake(WHATHERE.x, WHATHERE.y,
155, 155)];
cv.radius = 75;
cv.strokeWidth = 5;
The radius of the circle is 75. The width/height of the CircleView is 155 because the stroke of the circle is 5. (radius x 2) + 5, which give me the view you see with the circle.
The dark background is the view of the iOS simulator. I have added background colors to each of the elements to distinguish their size.
Through the magic of Photoshop here is what I am trying to accomplish:
Then you're doing something wrong... Use constraints !
Here is my Storyboard label constraints
Here my ViewController
#interface ViewController ()
#property (nonatomic, weak) IBOutlet UILabel *centeredLabel;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
CircleView *circleView = [CircleView new];
[self.view addSubview:circleView];
circleView.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *circleCenterX = [NSLayoutConstraint constraintWithItem:circleView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.centeredLabel
attribute:NSLayoutAttributeCenterX
multiplier:1 constant:0];
NSLayoutConstraint *circleCenterY = [NSLayoutConstraint constraintWithItem:circleView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.centeredLabel
attribute:NSLayoutAttributeCenterY
multiplier:1 constant:0];
CGFloat circleDiameter = 155;
NSLayoutConstraint *circleWidth = [NSLayoutConstraint constraintWithItem:circleView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1 constant:circleDiameter];
NSLayoutConstraint *circleHeight = [NSLayoutConstraint constraintWithItem:circleView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:circleView
attribute:NSLayoutAttributeWidth
multiplier:1 constant:0];
[self.view addConstraints:#[circleCenterX, circleCenterY, circleWidth, circleHeight]];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Here's CircleView
#implementation CircleView
- (instancetype)init{
self = [super init];
if (self) {
self.backgroundColor = [UIColor clearColor];
}
return self;
}
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
UIColor *blueTransparent = [[UIColor blueColor] colorWithAlphaComponent:0.4];
[blueTransparent setFill];
CGContextFillRect(ctx, self.bounds);
UIColor *circleColor = [UIColor greenColor];
[circleColor setStroke];
CGContextSetLineWidth(ctx, 6);
CGContextStrokeEllipseInRect(ctx, self.bounds);
}
#end
And here's the result
Piece of cake ! ;)
Here's what I would do:
Make sure there are no constraints that are potentially pushing the circle
Make the circle view a subview of NOT the UILabel, but of the superview (you're probably already doing this)
set the circle view's frame to
CGRectMake(self.myLabel.frame.origin.x - (155 / 2.0), self.myLabel.frame.origin.y - (155 / 2.0), 155, 155);
Do The init of circle in viewDidAppear instead of viewDidLoad

Draw and Center a Custom UIView as Background Using Auto Layout

I have a BoardViewController (UIViewController) and need to draw centered coordinate lines into its background. For these coordinate lines I created a custom UIView class CoordinateView which are added as subView. The coordinateView should be centered and fill the whole screen even when changing the device orientation.
To do this I'd like to use Auto Layout implemented in code. Here's my current setup:
In the CoordinatesView (UIView) class a custom draw method for the coordinate lines
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextSetLineWidth(context, 1.0);
CGContextMoveToPoint(context, self.bounds.size.width/2,0);
CGContextAddLineToPoint(context, self.bounds.size.width/2,self.bounds.size.height);
CGContextStrokePath(context);
CGContextMoveToPoint(context, 0,self.bounds.size.height/2);
CGContextAddLineToPoint(context, self.bounds.size.width,self.bounds.size.height/2);
CGContextStrokePath(context);
}
Initializing this coordinatesView object in the BoardViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
...
coordinatesView = [[CoordinatesView alloc]initWithFrame:self.view.frame];
[coordinatesView setBackgroundColor:[UIColor redColor]];
[coordinatesView clipsToBounds];
[coordinatesView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addSubview:coordinatesView];
[self.view sendSubviewToBack:coordinatesView];
...
}
Adding the auto layout magic to the coordinateView in the BoardViewController's viewWillAppear function
-(void)viewWillAppear:(BOOL)animated{
...
NSLayoutConstraint *constraintCoordinatesCenterX =[NSLayoutConstraint
constraintWithItem:self.view
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:coordinatesView
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:1];
NSLayoutConstraint *constraintCoordinatesCenterY =[NSLayoutConstraint
constraintWithItem:self.view
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:coordinatesView
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:1];
[self.view addConstraint: constraintCoordinatesCenterX];
[self.view addConstraint: constraintCoordinatesCenterY];
...
}
Note: This approach worked for me using an UIImageView Image as coordinates but it doesn't work with the custom UIView coordinateView.
How do I make it work again? As soons as I apply the Auto Layout/NSLayoutConstraint my coordinatesView UIView seems disappears
Is this actually a good approach to add a background drawing to a UIViewController or is it better to directly draw into the UIViewController. (If so how would that look like?)
I appreciate your help with this.
Your view disappears because you set no size constraints -- you generally need 4 constraints to fully express the position and size of a view. Some views, like buttons have an intrinsic content size, so you don't need to explicitly set the size. The same is true for image views, which get their size from the image they display.
So, in your case, you can set the width and height equal to the width and height of the superview. This is something that I do often, so I've created a category on UIView that contains various constraint methods, so I don't have to write this over and over. I have one method constrainViewEqual: that does what you want to do:
#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];
}
Then, in your main code, you can call it like this:
[self.view constrainViewsEqual:coordinatesView];

Replicating iBooks zoom/fade animation

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.

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