Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating parallax focus effect on UICollectionViewCell

How do you create the parallax focus effect on a collection view cell with a custom view? If I were using an image view the property to set would be adjustsImageWhenAncestorFocused but my collection view cell contains a subclassed UIView with custom content drawn using core graphics.

like image 691
Berry Blue Avatar asked Dec 01 '15 19:12

Berry Blue


3 Answers

The answer by @raulriera is nice, but only shifts the cell around in 2D. Also, the OP asked for an objective-C example.

I was also looking to do this effect for the exact same reason - I had UICollectionView with cells containing images and labels.

I created a UIMotionEffectGroup subclass, since getting near to the Apple TV effect seems to require four different motion effects. The first two are the flat movements as in @raulriera, and the other two are the 3D rotations.

Just the shiny environment layer to go now. Any takers? :-)

Here is my code for the motion effect group:

(The shiftDistance and tiltAngle constants set the magnitude of the effect. The given values look pretty similar to the Apple TV effect.)

#import <UIKit/UIKit.h>
#import "UIAppleTvMotionEffectGroup.h"

@implementation UIAppleTvMotionEffectGroup

- (id)init
{
    if ((self = [super init]) != nil)
    {
        // Size of shift movements
        CGFloat const shiftDistance = 10.0f;

        // Make horizontal movements shift the centre left and right
        UIInterpolatingMotionEffect *xShift = [[UIInterpolatingMotionEffect alloc]
                                              initWithKeyPath:@"center.x"
                                              type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
        xShift.minimumRelativeValue = [NSNumber numberWithFloat: shiftDistance * -1.0f];
        xShift.maximumRelativeValue = [NSNumber numberWithFloat: shiftDistance];

        // Make vertical movements shift the centre up and down
        UIInterpolatingMotionEffect *yShift = [[UIInterpolatingMotionEffect alloc]
                                              initWithKeyPath:@"center.y"
                                              type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];
        yShift.minimumRelativeValue = [NSNumber numberWithFloat: shiftDistance * -1.0f];
        yShift.maximumRelativeValue = [NSNumber numberWithFloat: shiftDistance];

        // Size of tilt movements
        CGFloat const tiltAngle = M_PI_4 * 0.125;

        // Now make horizontal movements effect a rotation about the Y axis for side-to-side rotation.
        UIInterpolatingMotionEffect *xTilt = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"layer.transform" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];

        // CATransform3D value for minimumRelativeValue
        CATransform3D transMinimumTiltAboutY = CATransform3DIdentity;
        transMinimumTiltAboutY.m34 = 1.0 / 500;
        transMinimumTiltAboutY = CATransform3DRotate(transMinimumTiltAboutY, tiltAngle * -1.0, 0, 1, 0);

        // CATransform3D value for maximumRelativeValue
        CATransform3D transMaximumTiltAboutY = CATransform3DIdentity;
        transMaximumTiltAboutY.m34 = 1.0 / 500;
        transMaximumTiltAboutY = CATransform3DRotate(transMaximumTiltAboutY, tiltAngle, 0, 1, 0);

        // Set the transform property boundaries for the interpolation
        xTilt.minimumRelativeValue = [NSValue valueWithCATransform3D: transMinimumTiltAboutY];
        xTilt.maximumRelativeValue = [NSValue valueWithCATransform3D: transMaximumTiltAboutY];

        // Now make vertical movements effect a rotation about the X axis for up and down rotation.
        UIInterpolatingMotionEffect *yTilt = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"layer.transform" type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];

        // CATransform3D value for minimumRelativeValue
        CATransform3D transMinimumTiltAboutX = CATransform3DIdentity;
        transMinimumTiltAboutX.m34 = 1.0 / 500;
        transMinimumTiltAboutX = CATransform3DRotate(transMinimumTiltAboutX, tiltAngle * -1.0, 1, 0, 0);

        // CATransform3D value for maximumRelativeValue
        CATransform3D transMaximumTiltAboutX = CATransform3DIdentity;
        transMaximumTiltAboutX.m34 = 1.0 / 500;
        transMaximumTiltAboutX = CATransform3DRotate(transMaximumTiltAboutX, tiltAngle, 1, 0, 0);

        // Set the transform property boundaries for the interpolation
        yTilt.minimumRelativeValue = [NSValue valueWithCATransform3D: transMinimumTiltAboutX];
        yTilt.maximumRelativeValue = [NSValue valueWithCATransform3D: transMaximumTiltAboutX];

        // Add all of the motion effects to this group
        self.motionEffects = @[xShift, yShift, xTilt, yTilt];

        [xShift release];
        [yShift release];
        [xTilt release];
        [yTilt release];
    }
    return self;
}

@end

I used it like this in my custom UICollectionViewCell subclass:

@implementation MyCollectionViewCell

- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator
{
    // Create a static instance of the motion effect group (could do this anywhere, really, maybe init would be better - we only need one of them.)
    static UIAppleTVMotionEffectGroup *s_atvMotionEffect = nil;
    if (s_atvMotionEffect == nil)
    {
        s_atvMotionEffect = [[UIAppleTVMotionEffectGroup alloc] init];
    }

    [coordinator addCoordinatedAnimations: ^{
        if (self.focused)
        {
            [self addMotionEffect: s_atvMotionEffect];
        }
        else
        {
            [self removeMotionEffect: s_atvMotionEffect];
        }

                               completion: ^{

            }];
}

@end
like image 123
Simon Tillson Avatar answered Nov 02 '22 01:11

Simon Tillson


All you need to do is add a UIMotionEffect to your subviews. Something like this

override func didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
    coordinator.addCoordinatedAnimations({ [unowned self] in
        if self.focused {
            let verticalMotionEffect = UIInterpolatingMotionEffect(keyPath: "center.y", type: .TiltAlongVerticalAxis)
            verticalMotionEffect.minimumRelativeValue = -10
            verticalMotionEffect.maximumRelativeValue = 10

            let horizontalMotionEffect = UIInterpolatingMotionEffect(keyPath: "center.x", type: .TiltAlongHorizontalAxis)
            horizontalMotionEffect.minimumRelativeValue = -10
            horizontalMotionEffect.maximumRelativeValue = 10

            let motionEffectGroup = UIMotionEffectGroup()
            motionEffectGroup.motionEffects = [horizontalMotionEffect, verticalMotionEffect]

            yourView.addMotionEffect(motionEffectGroup)
        }
        else {
            // Remove the effect here
        }
        }, completion: nil)
}
like image 6
raulriera Avatar answered Nov 02 '22 00:11

raulriera


I've converted Simon Tillson's answer to swift 3.0 and posted here to save typing for people in the future. Thanks very much for a great solution.

class UIAppleTVMotionEffectGroup : UIMotionEffectGroup{
// size of shift movements
let shiftDistance : CGFloat = 10.0
let tiltAngle : CGFloat = CGFloat(M_PI_4) * 0.125

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}

override init() {
    super.init()
    // Make horizontal movements shift the centre left and right
    let xShift = UIInterpolatingMotionEffect(keyPath: "center.x", type: UIInterpolatingMotionEffectType.tiltAlongHorizontalAxis)
    xShift.minimumRelativeValue = shiftDistance * -1.0
    xShift.maximumRelativeValue = shiftDistance

    let yShift = UIInterpolatingMotionEffect(keyPath: "center.y", type: UIInterpolatingMotionEffectType.tiltAlongVerticalAxis)
    yShift.minimumRelativeValue = 0.0-shiftDistance
    yShift.maximumRelativeValue = shiftDistance

    let xTilt = UIInterpolatingMotionEffect(keyPath: "layer.transform", type: UIInterpolatingMotionEffectType.tiltAlongHorizontalAxis)

    var transMinimumTiltAboutY = CATransform3DIdentity
    transMinimumTiltAboutY.m34 = 1.0 / 500.0
    transMinimumTiltAboutY = CATransform3DRotate(transMinimumTiltAboutY, tiltAngle * -1.0, 0, 1, 0)

    var transMaximumTiltAboutY = CATransform3DIdentity
    transMaximumTiltAboutY.m34 = 1.0 / 500.0
    transMaximumTiltAboutY = CATransform3DRotate(transMaximumTiltAboutY, tiltAngle , 0, 1, 0)

    xTilt.minimumRelativeValue = transMinimumTiltAboutY
    xTilt.maximumRelativeValue = transMaximumTiltAboutY

    let yTilt = UIInterpolatingMotionEffect(keyPath: "layer.transform", type: UIInterpolatingMotionEffectType.tiltAlongVerticalAxis)

    var transMinimumTiltAboutX = CATransform3DIdentity
    transMinimumTiltAboutX.m34 = 1.0 / 500.0
    transMinimumTiltAboutX = CATransform3DRotate(transMinimumTiltAboutX, tiltAngle * -1.0, 1, 0, 0)

    var transMaximumTiltAboutX = CATransform3DIdentity
    transMaximumTiltAboutX.m34 = 1.0 / 500.0
    transMaximumTiltAboutX = CATransform3DRotate(transMaximumTiltAboutX, tiltAngle , 1, 0, 0)

    yTilt.minimumRelativeValue = transMinimumTiltAboutX
    yTilt.maximumRelativeValue = transMaximumTiltAboutX

    self.motionEffects = [xShift,yShift,xTilt,yTilt]

}

}

I have added a little pop to the part in the UICollectionView subclass. Note the struct wrapper for the static variable

  override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {

    struct wrapper {
        static let s_atvMotionEffect = UIAppleTVMotionEffectGroup()
    }


    coordinator.addCoordinatedAnimations( {
        var scale : CGFloat = 0.0
        if self.isFocused {
            self.addMotionEffect(wrapper.s_atvMotionEffect)
            scale = 1.2
        } else {
            self.removeMotionEffect(wrapper.s_atvMotionEffect)
            scale = 1.0
        }
        let transform = CGAffineTransform(scaleX: scale, y: scale)
        self.layer.setAffineTransform(transform)
    },completion: nil)


}
like image 6
Ajaxharg Avatar answered Nov 02 '22 01:11

Ajaxharg