Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement IFTTTMaskEffectRevealFromCenter animation in IFTTTMaskAnimation #1701

Merged
merged 2 commits into from Sep 15, 2015

Conversation

jsm174
Copy link
Contributor

@jsm174 jsm174 commented Sep 8, 2015

Not sure if anyone else would find this useful, but I implemented a circular mask animation. This animation will mask a view to a circle starting in the center at visibility 0 and work it's way to the outside at visibility 1.0.

Initially I thought I could use the corner radius animation for what I needed, but I wanted the radius to be calculated automatically.

@jsm174 jsm174 changed the title Circular mask Implement circular mask animation Sep 8, 2015
@ashavit
Copy link

ashavit commented Sep 8, 2015

@jsm174 Seems great. I like it.
I suggest to add the effect as another enum to the IFTTTMaskAnimation instead of another class.
Using the enum you can also add the mask effect as hiding a view as well as revealing it (animate both ways).

@jsm174
Copy link
Contributor Author

jsm174 commented Sep 8, 2015

@ashavit Thank you. I just attempted a quick merge of this into your class. Could you give me some thoughts on this:

typedef NS_ENUM(NSUInteger, IFTTTMaskSwipeDirection)
{
    IFTTTMaskSwipeDirectionFromTop,
    IFTTTMaskSwipeDirectionFromLeft,
    IFTTTMaskSwipeDirectionFromBottom,
    IFTTTMaskSwipeDirectionFromRight,
    IFTTTMaskSwipeDirectionFromCenter
};

typedef NS_ENUM(NSUInteger, IFTTTMaskSwipeEffect)
{
    IFTTTMaskSwipeEffectRect,
    IFTTTMaskSwipeEffectCircle
};

@interface IFTTTMaskAnimation : IFTTTAnimation <IFTTTAnimatable>

- (instancetype)initWithView:(UIView *)view direction:(IFTTTMaskSwipeDirection)direction effect:(IFTTTMaskSwipeEffect)effect;
+ (instancetype)animationWithView:(UIView *)view direction:(IFTTTMaskSwipeDirection)direction effect:(IFTTTMaskSwipeEffect)effect;

- (void)addKeyframeForTime:(CGFloat)time visibility:(CGFloat)percent;
- (void)addKeyframeForTime:(CGFloat)time visibility:(CGFloat)percent withEasingFunction:(IFTTTEasingFunction)easingFunction;

- (void)animate:(CGFloat)time
{
    if (!self.hasKeyframes) return;
    CGFloat visibilityPercent = ((NSNumber *)[self valueAtTime:time]).floatValue;

    CGRect maskedRect = self.maskedView.bounds;
    CGFloat radius = MAX((maskedRect.size.width / 2), (maskedRect.size.height / 2));

    switch (self.direction)
    {
        case IFTTTMaskSwipeDirectionFromTop:
        {
            maskedRect.size.height *= visibilityPercent;
            break;
        }
        case IFTTTMaskSwipeDirectionFromLeft:
        {
            maskedRect.size.width *= visibilityPercent;
            break;
        }
        case IFTTTMaskSwipeDirectionFromBottom:
        {
            maskedRect.size.height *= visibilityPercent;
            maskedRect.origin.y = CGRectGetMaxY(self.maskedView.bounds) - maskedRect.size.height;
            break;
        }
        case IFTTTMaskSwipeDirectionFromRight:
        {
            maskedRect.size.width *= visibilityPercent;
            maskedRect.origin.x = CGRectGetMaxX(self.maskedView.bounds) - maskedRect.size.width;
            break;
        }
        case IFTTTMaskSwipeDirectionFromCenter:
        {
            CGFloat inset = radius - (radius * visibilityPercent);
            maskedRect = CGRectInset(maskedRect, inset, inset);
            break;
        }

        default:
            break;
    }

    UIBezierPath *maskPath = null;

    if (effect == IFTTTMaskSwipeEffectCircle) {
       maskPath = [UIBezierPath bezierPathWithRoundedRect:maskedRect cornerRadius:radius];
    }
    else {
       maskPath = [UIBezierPath bezierPathWithRect:maskedRect];
    }

    CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
    maskLayer.path = maskPath.CGPath;
    self.maskedView.layer.mask = maskLayer;
}

@lauraskelton
Copy link
Contributor

@jsm174 What if you do something more like this, to make the names of the enum in the existing mask class make more sense as a general mask (since the circle isn't really "swiping" in the same way).

typedef NS_ENUM(NSUInteger, IFTTTMaskEffect)
{
    IFTTTMaskEffectRevealFromTop,
    IFTTTMaskEffectRevealFromLeft,
    IFTTTMaskEffectRevealFromBottom,
    IFTTTMaskEffectRevealFromRight,
    IFTTTMaskEffectRevealFromCenter
};

@interface IFTTTMaskAnimation : IFTTTAnimation <IFTTTAnimatable>

- (instancetype)initWithView:(UIView *)view maskEffect:(IFTTTMaskEffect)maskEffect;
+ (instancetype)animationWithView:(UIView *)view maskEffect:(IFTTTMaskEffect)maskEffect;

- (void)addKeyframeForTime:(CGFloat)time visibility:(CGFloat)percent;
- (void)addKeyframeForTime:(CGFloat)time visibility:(CGFloat)percent withEasingFunction:(IFTTTEasingFunction)easingFunction;
- (void)animate:(CGFloat)time
{
    if (!self.hasKeyframes) return;
    CGFloat visibilityPercent = ((NSNumber *)[self valueAtTime:time]).floatValue;

    UIBezierPath *maskPath;
    CGRect maskedRect = self.maskedView.bounds;
    switch (self.maskEffect)
    {
        case IFTTTMaskEffectRevealFromTop:
        {
            maskedRect.size.height *= visibilityPercent;
            maskPath = [UIBezierPath bezierPathWithRect:maskedRect];
            break;
        }
        case IFTTTMaskEffectRevealFromLeft:
        {
            maskedRect.size.width *= visibilityPercent;
            maskPath = [UIBezierPath bezierPathWithRect:maskedRect];
            break;
        }
        case IFTTTMaskEffectRevealFromBottom:
        {
            maskedRect.size.height *= visibilityPercent;
            maskedRect.origin.y = CGRectGetMaxY(self.maskedView.bounds) - CGRectGetHeight(maskedRect);
            maskPath = [UIBezierPath bezierPathWithRect:maskedRect];
            break;
        }
        case IFTTTMaskEffectRevealFromRight:
        {
            maskedRect.size.width *= visibilityPercent;
            maskedRect.origin.x = CGRectGetMaxX(self.maskedView.bounds) - CGRectGetWidth(maskedRect);
            maskPath = [UIBezierPath bezierPathWithRect:maskedRect];
            break;
        }
        case IFTTTMaskEffectRevealFromCenter:
        {
            CGPoint center = CGPointMake((CGRectGetWidth(maskedRect) / 2.f), (CGRectGetHeight(maskedRect) / 2.f));
            CGFloat radius = MAX(center.x, center.y);
            maskPath = [UIBezierPath bezierPathWithArcCenter:center
                                                      radius:radius * visibilityPercent
                                                  startAngle:0.f
                                                    endAngle:M_PI * 2.f
                                                   clockwise:YES];
            break;
        }

        default:
            break;
    }

    CAShapeLayer *maskLayer = [CAShapeLayer new];
    maskLayer.path = maskPath.CGPath;
    self.maskedView.layer.mask = maskLayer;
}

@lauraskelton
Copy link
Contributor

This looks like it will be a great addition!

with maskEffect. Added new animation for IFTTTMaskEffectRevealFromCenter
@jsm174 jsm174 reopened this Sep 14, 2015
@jsm174 jsm174 changed the title Implement circular mask animation Implement IFTTTMaskEffectRevealFromCenter animation in IFTTTMaskAnimation Sep 14, 2015
@jsm174
Copy link
Contributor Author

jsm174 commented Sep 14, 2015

@lauraskelton Great suggestions. Implemented your changes in this pull request. (Tested as well too in my own app).

@lauraskelton
Copy link
Contributor

@jsm174 This is looking really great! I tested it out, and noticed that when the view it's masking is not square, it behaves in a way I don't totally expect, masking the view out to a part-rectangle with a circle on the narrow end.

I can imagine 2 desirable uses for this circle mask animation - one is when you want to reveal a view up until it's a circle inset in the view's bounds. The final 100% visible state would look like this:

simulator screen shot sep 14 2015 6 28 08 pm

Another is when you want to reveal the view fully, so that in the end it's showing its full rectangular bounds, like this:

simulator screen shot sep 14 2015 6 27 55 pm

I think we need to do something more like this to differentiate between the two cases-

        case IFTTTMaskEffectRevealFromCenterToCircle:
        {
            CGPoint center = CGPointMake((CGRectGetWidth(maskedRect) / 2.f), (CGRectGetHeight(maskedRect) / 2.f));
            CGFloat radius = MIN(center.x, center.y);
            maskPath = [UIBezierPath bezierPathWithArcCenter:center
                                                      radius:radius * visibilityPercent
                                                  startAngle:0.f
                                                    endAngle:M_PI * 2.f
                                                   clockwise:YES];
            break;
        }
        case IFTTTMaskEffectRevealFromCenterToBounds:
        {
            CGPoint center = CGPointMake((CGRectGetWidth(maskedRect) / 2.f), (CGRectGetHeight(maskedRect) / 2.f));
            CGFloat radius = sqrt(pow(center.x, 2) + pow(center.y, 2));
            maskPath = [UIBezierPath bezierPathWithArcCenter:center
                                                      radius:radius * visibilityPercent
                                                  startAngle:0.f
                                                    endAngle:M_PI * 2.f
                                                   clockwise:YES];
            break;
        }

How does that sound?

IFTTTEffectRevealFromCenterToCircle. Added
IFTTTMaskEffectRevealFromCenterToBounds mask effect as per @lauraskelton
@jsm174
Copy link
Contributor Author

jsm174 commented Sep 15, 2015

@lauraskelton great! Implemented your code in last commit!

@lauraskelton
Copy link
Contributor

@jsm174 awesome, merging it in!

lauraskelton added a commit that referenced this pull request Sep 15, 2015
Implement IFTTTMaskEffectRevealFromCenter animation in IFTTTMaskAnimation
@lauraskelton lauraskelton merged commit b2e0d4c into IFTTT:master Sep 15, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants