Reputation: 3323
Wanting to use the code below in several view controllers (UIImageView fade in/fade out), but not wanting to have it coded in many locations. I have been researching the use of a category, protocol and extension for this. I know these concepts have been discussed here, but have not yet found an answer that speaks more general.
If I were to use a category, on which class would it be based? UIView? NSObject?
Apple's Working with Protocols seems to be a possible solution, and in it is this quote:
Protocols are also useful in situations where the class of an object isn’t known, or needs to stay hidden.
Please suggest why to use one over another.
dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:0.8 delay:0.5 options:0 animations: ^{
self.statusImageView.alpha = 1.0f;
}
completion: ^(BOOL finished) {
[UIView animateWithDuration:0.8 delay:2.5 options:0 animations: ^{
self.statusImageView.alpha = 0.0f;
}
completion: ^(BOOL finished) {
self.statusImageView.hidden = YES;
}];
}];
});
Upvotes: 2
Views: 70
Reputation: 52227
in this case I wouldn't go with a category or subclassing, but with composition over inheritance: Put your animation code in a class Animator
, add it as a property to a view controller or a UIView subclass. when the animation should occur, call a method on the animator that will animate a given view.
You now can configure the animator for different situation and reuse it where needed. You also can subclass the animator needed.
Animator.h
@protocol Animator <NSObject>
-(void)animate;
@end
@interface Animator : NSObject <Animator>
-(instancetype)initWithView:(UIView *)view;
@end
@interface FadeInAndOutAnimator : Animator
-(instancetype)initWithView:(UIView *)view
fadeInDuration:(CGFloat)fadeInDuration
fadeOutDuration:(CGFloat)fadeOutDuration;
-(instancetype)initWithView:(UIView *)view
fadeInDuration:(CGFloat)fadeInDuration
fadeOutDuration:(CGFloat)fadeOutDuration
fadeInDelay:(CGFloat) fadeInDelay
fadeOuDelay:(CGFloat) fadeOutDelay;
@end
Animator.m
#import "Animator.h"
@interface Animator ()
@property(nonatomic, weak) UIView *view;
@end
@implementation Animator
-(instancetype)initWithView:(UIView *)view
{
self = [super init];
if (self){
self.view = view;
}
return self;
}
-(void)animate {
NSAssert(NO, @"%@ must be overwritten in a subclass", NSStringFromSelector(_cmd));
}
@end
@interface FadeInAndOutAnimator ()
@property CGFloat fadeOutDuration;
@property CGFloat fadeInDuration;
@property CGFloat fadeInDelay;
@property CGFloat fadeOutDelay;
@end
@implementation FadeInAndOutAnimator
-(instancetype)initWithView:(UIView *)view
fadeInDuration:(CGFloat)fadeInDuration
fadeOutDuration:(CGFloat)fadeOutDuration
{
return [self initWithView:view
fadeInDuration:fadeInDuration
fadeOutDuration:fadeOutDuration
fadeInDelay:.8
fadeOuDelay:2.5];
}
-(instancetype)initWithView:(UIView *)view
fadeInDuration:(CGFloat)fadeInDuration
fadeOutDuration:(CGFloat)fadeOutDuration
fadeInDelay:(CGFloat)fadeInDelay
fadeOuDelay:(CGFloat)fadeOutDelay
{
self = [super initWithView:view];
if(self){
self.fadeOutDuration = fadeOutDuration;
self.fadeInDuration = fadeInDuration;
self.fadeInDelay = fadeInDelay;
self.fadeOutDelay = fadeOutDelay;
}
return self;
}
-(void)animate
{
[UIView animateWithDuration:self.fadeInDuration delay:self.fadeInDelay options:0 animations: ^{
self.view.alpha = 1.0f;
}
completion: ^(BOOL finished) {
[UIView animateWithDuration:self.fadeOutDuration delay:self.fadeOutDelay options:0 animations: ^{
self.view.alpha = 0.0f;
}
completion: ^(BOOL finished) {
}];
}];
}
@end
ViewController.m
#import "ViewController.h"
#import "Animator.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *statusView;
@property (strong, nonatomic) id<Animator> animator;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (IBAction)animateTapped:(id)sender {
self.animator = [[FadeInAndOutAnimator alloc] initWithView:self.statusView
fadeInDuration:.8
fadeOutDuration:.5];
[self.animator animate];
}
- (IBAction)animateFastTapped:(id)sender {
self.animator = [[FadeInAndOutAnimator alloc] initWithView:self.statusView
fadeInDuration:.8
fadeOutDuration:.5
fadeInDelay:0
fadeOuDelay:0];
[self.animator animate];
}
@end
I published a full project at GitLab
Note that Animator
is also a protocol. The view controllers accepts any object that implements this protocol. The class Animator
now is abstract in the way that animate
must be overwritten.
Upvotes: 4
Reputation: 557
As your animating a custom property statusImageView of your view controller a protocol extension and a default implementation containing the actual animation code might be a good combination in order to reuse the animation code.
An example protocol could look like this, were you define all variables that need to exist in conforming types and the signature of the animation function that will be provided in the default implementation:
protocol StatusAnimatable {
var statusView: UIView { get }
func animateStatusView()
}
I the default implementation we could then add the actual animation code:
extension StatusAnimatable {
func animateStatusView() {
UIView.animateWithDuration(0.3) {
// Perform all animations using statusView
self.statusView.alpha = 1
}
}
}
Now we are able to conform any UIViewController that has a statusView property to our protocol and call animateStatusView on it.
EDIT: It seems you want to perform this specific fade animation on any UIView if that would be the case you can simply add an extension on UIView containing the specific fading animation code you want to perform:
extension UIView {
func fadeIn(duration: NSTimeInterval = 0.5, delay: NSTimeInterval = 0, completion: (() -> Void)? = nil) {
UIView.animateWithDuration(duration, delay: delay, options: [], animations: { () -> Void in
self.alpha = 1.0
},
completion: { _ in
completion?()
})
}
func fadeOut(duration: NSTimeInterval = 0.5, delay: NSTimeInterval = 0, completion: (() -> Void)? = nil) {
UIView.animateWithDuration(duration, delay: delay, options: [], animations: { () -> Void in
self.alpha = 0
},
completion: { _ in
self.hidden = true
completion?()
})
}
}
And then chain both functions later:
let someView = UIView()
someView.fadeIn {
someView.fadeOut()
}
Upvotes: 1
Reputation: 4111
Category on UIView. Create a new file (File\New\New File) and choose the iOS\Cocoa Touch\Objective-C category template. Enter “Animation” for the Category and “UIView” for Category on, and finish creating the file. A category allows us to add some methods to an already existing class (UIView in this case) without subclassing it: a very neat and powerful way to expand an object’s functionality. We’ll put all the animation code this category.
In UIView+Animation.h:
- (void) moveTo:(CGPoint)destination duration:(float)duration delay:(float)delay option:(UIViewAnimationOptions)option;
Then add the implementation in UIView+Animation.m as follows:
- (void) moveTo:(CGPoint)destination duration:(float)duration delay:(float)delay option:(UIViewAnimationOptions)option
{
[UIView animateWithDuration:duration delay:delay options:option
animations:^{
self.frame = CGRectMake(destination.x,destination.y, self.frame.size.width, self.frame.size.height);
}
completion:nil];
}
Uses:
#import "UIView+Animation.h"
Somewhere in code where you decided to have animation:
UIButton* movingButton= //initialization code of button or UIView or say any subclass of UIView
[movingButton moveTo:
CGPointMake(button.center.x - (movingButton.frame.size.width/2),
button.frame.origin.y - (movingButton.frame.size.height + 5.0))
duration:1.0 option:0]; // above the tapped button
Hope this will help you to understand basic flow.
Apart from basic concept there is already a nice categoryUIView+Animation
Upvotes: 1