문제

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];
        }
    }];
}
도움이 되었습니까?

해결책

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];
}

다른 팁

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);
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top