Frage

I've never worked with animations on iOS specifically and was hoping to be pointed in the right direction. I have so far animated a bunch of pill shaped objects in a fashion similar to what I have below, however I have yet to implement the correct masking based on the y positioning of the shape.

sine wave

The pill shapes are just UIViews. I'm looking for a way to get their backgrounds to change based on their positioning; the background itself does not move, just the pills.

EDIT:

Based on Rob's answer below, I am now tying the animation to the device's movement like so:

- (void)setAnimationToDeviceMotion {
    motionManager = [[CMMotionManager alloc] init];
    motionManager.deviceMotionUpdateInterval = 0.1f;
    NSOperationQueue *motionQueue = [[NSOperationQueue alloc] init];

    [motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXArbitraryZVertical toQueue:motionQueue withHandler:^(CMDeviceMotion *motion, NSError *error) {
        if (!error) {
            double pitch = motion.attitude.pitch;
            NSLog(@"%f", pitch);
            [self addMaskToView:self.imageView withAdjustment:pitch];
        }
    }];
}
War es hilfreich?

Lösung

Assuming this is eight pill shapes that will reveal a single image underneath, you

  • create a UIBezierPath for the eight pill shapes
  • create a CAShapeLayer layer that uses that UIBezierPath (or more accurately, its CGPath)
  • apply this CAShapeLayer as the mask for the layer for your UIImageView

If you want to animate this over time, just replace the mask (e.g. in response to a accelerometer event, a CADisplayLink or NSTimer, or a UIPanGesture), or just update the path of the CAShapeLayer you have applied as the mask to the UIImageView's layer.

Thus, you might have something like:

- (void)addMaskToView:(UIView *)view withAdjustment:(CGFloat)adjustment
{
    CAShapeLayer *mask = [CAShapeLayer layer];
    mask.path = [[self pillsPathWithAdjustment:adjustment forView:view] CGPath];

    self.imageView.layer.mask = mask;
}

- (UIBezierPath *)pillsPathWithAdjustment:(CGFloat)adjustment forView:(UIView *)view
{
    UIBezierPath *path = [UIBezierPath bezierPath];

    for (NSInteger i = 0; i < kNumberOfPills; i++)
        [self addPillPathNumber:i forView:view toPath:path adjustment:adjustment];

    return path;
}

- (void)addPillPathNumber:(NSInteger)index forView:(UIView *)view toPath:(UIBezierPath *)path adjustment:(CGFloat)adjustment
{
    CGFloat const pillSpacing = 5.0f;
    CGFloat const pillWidth = view.bounds.size.width / kNumberOfPills - pillSpacing;
    CGFloat const pillHeight =  view.bounds.size.height * 0.4;
    CGFloat const cornerRounding = 10.0f;

    CGPoint point = CGPointMake(index * (pillWidth + pillSpacing) + pillSpacing / 2.0, sinf((float) index * M_PI / kNumberOfPills + adjustment * M_PI * 2.0) * 100.0);

    // top

    point.x += cornerRounding;
    [path moveToPoint:point];
    point.x += pillWidth - cornerRounding * 2.0;
    [path addLineToPoint:point];

    // top right corner

    [path addArcWithCenter:CGPointMake(point.x, point.y + cornerRounding) radius:cornerRounding startAngle:3 * M_PI_2 endAngle:2.0 * M_PI clockwise:YES];
    point.x += cornerRounding;
    point.y += cornerRounding;

    // right

    point.y += pillHeight - cornerRounding * 2.0;
    [path addLineToPoint:point];

    // lower right corner

    [path addArcWithCenter:CGPointMake(point.x - cornerRounding, point.y) radius:cornerRounding startAngle:0 endAngle:M_PI_2 clockwise:YES];

    // bottom

    point.y += cornerRounding;
    point.x -= cornerRounding;
    point.x -= pillWidth - cornerRounding * 2.0;
    [path addLineToPoint:point];

    // lower left corner

    [path addArcWithCenter:CGPointMake(point.x, point.y - cornerRounding) radius:cornerRounding startAngle:M_PI_2 endAngle:M_PI clockwise:YES];

    // left

    point.y -= cornerRounding;
    point.x -= cornerRounding;
    point.y -= pillHeight - cornerRounding * 2.0;
    [path addLineToPoint:point];

    // upper left corner

    [path addArcWithCenter:CGPointMake(point.x + cornerRounding, point.y) radius:cornerRounding startAngle:M_PI endAngle:3.0 * M_PI_2 clockwise:YES];

    [path closePath];
}

By way of illustration, this is how I'd animate this using a CADisplayLink:

- (void)startDisplayLink
{
    self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)];
    self.startTime = CACurrentMediaTime();
    [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}

- (void)stopDisplayLink
{
    [self.displayLink invalidate];
    self.displayLink = nil;
}

- (void)handleDisplayLink:(CADisplayLink *)displayLink
{
    CGFloat seconds;
    CGFloat fractionsOfSecond = modff(CACurrentMediaTime() - self.startTime, &seconds);

    [self addMaskToView:self.imageView withAdjustment:fractionsOfSecond];
}

Andere Tipps

To do masking, you'll need to work with the view's layers. Instead of moving the views themselves up and down, create views that occupy the whole height of the parent, and then create a CALayer in the pill shape. Set the pill layer as the view's layer's mask, and move it up and down.

Something like this:

UIImageView *myImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"redAndBlueBackground"]];

CALayer *pillLayer = [[CALayer alloc] initWithFrame:CGRectMake(0, 0, myImageView.bounds.size.width, myImageView.bounds.size.height / 2];
pillLayer.cornerRadius = 8;
myImageView.layer.maskLayer = pillLayer;

// move the pill layer to height y:
pillLayer.center = CGPointMake(myImageView.bounds.size.width / 2, 
                               (myImageView.bounds.size.height / 2) + y);
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top