I am trying to animate a flip effect between a UIButton instance and a UIImageView instance. Basically it's a "flip over a playing card" effect, one side (UIImageView) is just a nice pattern and when flipped, it should show a UIButton with some text.
My code features the following problems:
- the text of the UIButton sub-view is not shown after flipping
- the shadow disappears during the flip animation
Here's a visual representation of the goal:
Here you can download the very simple sample app.
Any suggestions how to solve the two mentioned issues ?
I am really out of ideas - any help highly appreciated!
Here's the header code:
#import <UIKit/UIKit.h>
@interface CardView : UIControl
@property (nonatomic) BOOL isFrontSide;
- (void)setupView;
- (void)turnCard:(BOOL)inShow withAnimationCompletion:(void (^)(BOOL inFinished))inCompletion;
@end
Here's the implementation code:
#import "CardView.h"
#import "UIView+Extension.h"
#import <QuartzCore/QuartzCore.h>
#define kAllControlStates (UIControlStateNormal | UIControlStateHighlighted | UIControlStateDisabled| UIControlStateSelected)
@interface CardView()
@end
@implementation CardView
- (void)setupView {
[self styleViewWithRoundedEdges:YES shadowed:YES];
UIImageView *theBackView = [[UIImageView alloc] initWithFrame:self.bounds];
theBackView.image = [UIImage imageNamed:@"pattern.png"];
theBackView.hidden = NO;
theBackView.userInteractionEnabled = NO;
[theBackView styleViewWithRoundedEdges:YES shadowed:NO];
[self addSubview:theBackView];
UIButton *theFrontView = [[UIButton alloc] initWithFrame:self.bounds];
[theFrontView setTitle:@"Push me !" forState:kAllControlStates];
theFrontView.hidden = YES;
theFrontView.userInteractionEnabled = NO;
[theFrontView styleViewWithRoundedEdges:YES shadowed:NO];
[self addSubview:theFrontView];
}
- (void)turnCard:(BOOL)inShow withAnimationCompletion:(void (^)(BOOL inFinished))inCompletion {
[UIView transitionWithView:self duration:0.75
options:inShow ? UIViewAnimationOptionTransitionFlipFromLeft : UIViewAnimationOptionTransitionFlipFromRight
animations:^{
[(self.subviews)[0] setHidden:inShow]; // UIImage
[(self.subviews)[1] setHidden:!inShow]; // UIButton
}
completion:inCompletion];
}
@end
Here's a category to visually decorate my views:
#import "UIView+Extension.h"
@implementation UIView (Extension)
- (void)styleViewWithRoundedEdges:(BOOL)rounded shadowed:(BOOL)shadowed {
[self styleViewWithRoundedEdges:rounded shadowed:shadowed rasterized:YES];
}
- (void)styleViewWithRoundedEdges:(BOOL)rounded shadowed:(BOOL)shadowed rasterized:(BOOL)rasterized {
if (rounded) {
self.layer.cornerRadius = 3.0;
}
if (shadowed) {
self.layer.shadowColor = [UIColor blackColor].CGColor;
self.layer.shadowOffset = CGSizeMake(2.0, 2.0);
self.layer.shadowOpacity = 0.25;
self.layer.shadowRadius = 1.0;
if(rasterized) {
self.layer.shouldRasterize = YES;
self.layer.rasterizationScale = UIScreen.mainScreen.scale;
}
}
}
@end
Here's my working solution with shadow, rounded corners and no performance issue:
Call [self.yourUICardButtonInstance setupWithImage:[UIImage imageNamed:@"your-image.png"]];
within the view controller's viewDidLayoutSubviews
callback.
Header:
//
// UICardButton.h
// CardFlipDemo
//
// Created by Nicolas Baumgardt on 25/08/15.
// Copyright (c) 2015 Nicolas Baumgardt. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface UICardButton : UIButton
- (void)setupWithImage:(UIImage*)backside;
- (void)flip;
- (void)flipFrontside;
- (void)flipBackside;
@end
Implementation:
//
// UICardButton.m
// CardFlipDemo
//
// Created by Nicolas Baumgardt on 25/08/15.
// Copyright (c) 2015 Nicolas Baumgardt. All rights reserved.
//
#import "UICardButton.h"
@interface UICardButton ()
@property (nonatomic) BOOL setup;
@property (nonatomic, strong) NSString* text;
@property (nonatomic, strong) UIImageView* subview;
@property (nonatomic) BOOL IBInspectable styled;
@property (nonatomic) BOOL IBInspectable frontside;
@end
@implementation UICardButton
- (void)setupWithImage:(UIImage*)backside {
if (!self.setup) {
// cache properties
self.text = self.currentTitle;
self.subview = [[UIImageView alloc] initWithFrame:self.bounds];
[self.subview setImage:backside];
// initialize card side
if (self.frontside) {
// frontside: with text
self.layer.transform = CATransform3DMakeRotation(0.0, 0.0, 1.0, 0.0);
[self setAttributedTitle:nil forState:UIControlStateNormal];
[self setTitle:self.text forState:UIControlStateNormal];
} else {
// backside: with image
self.layer.transform = CATransform3DMakeRotation(M_PI, 0.0, 1.0, 0.0);
[self addSubview:self.subview];
[self setAttributedTitle:nil forState:UIControlStateNormal];
[self setTitle:@"" forState:UIControlStateNormal];
}
// add a shadow by wrapping the button into a container and add rounded corners
if (self.styled) {
// add a shadow
self.layer.shadowColor = [UIColor blackColor].CGColor;
self.layer.shadowOffset = self.frontside ? CGSizeMake(2.0, 2.0) : CGSizeMake(-2.0, 2.0);
self.layer.shadowOpacity = 0.25;
self.layer.shadowRadius = 1.0;
self.layer.cornerRadius = 3.0;
self.layer.masksToBounds = NO;
// clip card image
self.subview.layer.masksToBounds = YES;
self.subview.layer.cornerRadius = 3.0;
// INFO: rasterization sometimes causes flickering, but enormous performance boost !
self.layer.shouldRasterize = YES;
self.layer.rasterizationScale = UIScreen.mainScreen.scale;
}
self.setup = YES;
}
}
- (void)flip {
if (self.frontside) {
[self flipBackside];
} else {
[self flipFrontside];
}
self.frontside = !self.frontside;
}
- (void)flipFrontside {
self.userInteractionEnabled = NO;
[UIView animateWithDuration:0.25 delay:0.0 options:UIViewAnimationOptionCurveEaseIn animations:^{
self.layer.transform = CATransform3DMakeRotation(M_PI_2, 0.0, 1.0, 0.0);
} completion:^(BOOL finished) {
[self.subview removeFromSuperview];
[self setAttributedTitle:nil forState:UIControlStateNormal];
[self setTitle:self.text forState:UIControlStateNormal];
self.layer.shadowOffset = CGSizeMake(2.0, 2.0);
[UIView animateWithDuration:0.25 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.layer.transform = CATransform3DMakeRotation(0.0, 0.0, 1.0, 0.0);
} completion:^(BOOL finished) {
self.userInteractionEnabled = YES;
}];
}];
}
- (void)flipBackside {
self.userInteractionEnabled = NO;
[UIView animateWithDuration:0.25 delay:0.0 options:UIViewAnimationOptionCurveEaseIn animations:^{
self.layer.transform = CATransform3DMakeRotation(M_PI_2, 0.0, 1.0, 0.0);
} completion:^(BOOL finished) {
[self addSubview:self.subview];
[self setAttributedTitle:nil forState:UIControlStateNormal];
[self setTitle:@"" forState:UIControlStateNormal];
self.layer.shadowOffset = CGSizeMake(-2.0, 2.0);
[UIView animateWithDuration:0.25 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.layer.transform = CATransform3DMakeRotation(M_PI, 0.0, 1.0, 0.0);
} completion:^(BOOL finished) {
self.userInteractionEnabled = YES;
}];
}];
}
@end
Try to use:
[UIView transitionFromView:self.subviews[0] toView:self.subviews[1] duration:0.5 UIViewAnimationOptionTransitionFlipFromLeft | UIViewAnimationOptionShowHideTransitionViews completion:^(BOOL finished) {
}];
This should work
[UIView transitionFromView:self.subviews[0] toView:self.subviews[1] duration:0.5 UIViewAnimationOptionTransitionFlipFromLeft | UIViewAnimationOptionShowHideTransitionViews completion:^(BOOL finished) {
}];
来源:https://stackoverflow.com/questions/32123244/ios-how-to-animate-a-flip-between-two-uiviews