David
David

Reputation: 3323

How make animation code available to multiple classes - Reusability

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

Answers (3)

vikingosegundo
vikingosegundo

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

Daehn
Daehn

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

maddy
maddy

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

Related Questions