114

I'm developing an app for iOS 7 in Objective-C. I've got a screen in my app with a few buttons and a pretty background image. (It's a simple xib with UIButtons on top of a UIImageView.)

I was thinking that it'd be cool if those buttons had the parallax effect that the iOS 7 home screen has, so if you tilt the phone you could see the background.

How can I implement that effect in my own app?

veducm
  • 5,933
  • 2
  • 34
  • 40
Dan Fabulich
  • 37,506
  • 41
  • 139
  • 175

9 Answers9

274

With iOS 7, Apple introduced UIMotionEffect to add Motion effects that are related to the orientation of the user’s device. For example, to emulate the parallax effect on the home screen, you can use the subclass UIInterpolatingMotionEffect, as explained here, just with a few lines of code.

Objective-C:

// Set vertical effect
UIInterpolatingMotionEffect *verticalMotionEffect = 
  [[UIInterpolatingMotionEffect alloc] 
  initWithKeyPath:@"center.y"
             type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];
verticalMotionEffect.minimumRelativeValue = @(-10);
verticalMotionEffect.maximumRelativeValue = @(10);

// Set horizontal effect 
UIInterpolatingMotionEffect *horizontalMotionEffect = 
  [[UIInterpolatingMotionEffect alloc] 
  initWithKeyPath:@"center.x"     
             type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
horizontalMotionEffect.minimumRelativeValue = @(-10);
horizontalMotionEffect.maximumRelativeValue = @(10);
  
// Create group to combine both
UIMotionEffectGroup *group = [UIMotionEffectGroup new];
group.motionEffects = @[horizontalMotionEffect, verticalMotionEffect];

// Add both effects to your view
[myBackgroundView addMotionEffect:group];

Swift (Thanks to @Lucas):

// Set vertical effect
let verticalMotionEffect = UIInterpolatingMotionEffect(keyPath: "center.y",
type: .TiltAlongVerticalAxis)
verticalMotionEffect.minimumRelativeValue = -10
verticalMotionEffect.maximumRelativeValue = 10

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

// Create group to combine both
let group = UIMotionEffectGroup()
group.motionEffects = [horizontalMotionEffect, verticalMotionEffect]

// Add both effects to your view
myBackgroundView.addMotionEffect(group)

Also, you can find a bunch of libraries to do this easier or to add this functionality to older iOS versions:

Rob
  • 415,655
  • 72
  • 787
  • 1,044
veducm
  • 5,933
  • 2
  • 34
  • 40
  • 2
    NGAParallaxMotion is for iOS 7+, not for older iOS versions. It provides a category for UIView, allowing you to set a .parallaxIntensity value, setting up the UIMotionEffect parameters with one line of code. – Dan Fabulich Sep 29 '13 at 20:44
  • 2
    Thanks! I have updated the answer to include supported versions and requirements. – veducm Oct 01 '13 at 09:57
  • 1
    Actually, you want to add the motion effect to the background view, not the front view. But other than that, this code works great! Thanks. – gstroup Oct 18 '13 at 23:53
  • Thanks @gstroup! I have updated the code to be 'myBackgroundView'. – veducm Oct 19 '13 at 10:52
  • 1
    Does anyone know how to check if the user has disabled the effects? http://stackoverflow.com/questions/19132000/is-it-possible-to-detect-if-parallax-effect-is-disabled-in-ios-7 – Eric Nov 05 '13 at 16:46
  • 3
    @gstroup Actually, on the home screen both the foreground and the background are moving. But if you were to chose only one layer to apply the parallax effect to, I agree the background would probably be best. – Rubberduck Jan 08 '14 at 08:19
  • @veducm: What about a parallax pinch zoom effect, how will this be done? – dcone Jun 26 '14 at 14:40
  • @dcone: not sure [this](http://stackoverflow.com/a/9167644/1264909) is what you are looking for. Otherwise, could you please give an example to understand what you want? – veducm Jul 02 '14 at 12:19
  • 1
    This worked right away, in a way that was exactly what I was after. Thank you so much! – Daniel Saidi Jan 19 '15 at 12:46
  • An important configuration that is necessary to think about for anyone using parallax, is that you need to consider the Z-axis layering of the view. If the view is to look "above", you will have a minimumRelativeValue less than the maximumRelativeValue. If the view is to look "behind" (such as iOS home/lock screen), then the values are to be reversed. – Ryan Mar 31 '15 at 02:26
  • Any ideas how to apply this so it just moves a single sprite kit SKSpriteNode inside an SKScene, rather than a whole view moving? – GilesDMiddleton Apr 22 '15 at 17:42
16

Translated to swift in case anyone is lazy. Please vote @veducm answer up if you found this useful

@IBOutlet var background : UIImageView!

func parallaxEffectOnBackground() {
    let relativeMotionValue = 50
    var verticalMotionEffect : UIInterpolatingMotionEffect = UIInterpolatingMotionEffect(keyPath: "center.y",
        type: .TiltAlongVerticalAxis)
    verticalMotionEffect.minimumRelativeValue = -relativeMotionValue
    verticalMotionEffect.maximumRelativeValue = relativeMotionValue

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

    var group : UIMotionEffectGroup = UIMotionEffectGroup()
    group.motionEffects = [horizontalMotionEffect, verticalMotionEffect]

    self.background.addMotionEffect(group)
}
Community
  • 1
  • 1
Lucas
  • 6,675
  • 3
  • 25
  • 43
  • Thanks @Lucas! I have updated my answer to include the swift sample too. It is based on this one. Feel free to review it and do any changes needed. – veducm Jan 12 '15 at 13:41
7

@veducm's solution can be a little shorter. The UIMotionEffectGroup for its x and y motion is obsolete if you add the the x and y-axis motionEffects separately.

UIInterpolatingMotionEffect *motionEffect;
motionEffect = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.x"
                                                               type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
motionEffect.minimumRelativeValue = @(-25);
motionEffect.maximumRelativeValue = @(25);
[bgView addMotionEffect:motionEffect];

motionEffect = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.y"
                                                               type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];
motionEffect.minimumRelativeValue = @(-25);
motionEffect.maximumRelativeValue = @(25);
[bgView addMotionEffect:motionEffect];
veducm
  • 5,933
  • 2
  • 34
  • 40
XOXO
  • 131
  • 1
  • 7
  • 2
    Just as a heads up if anyone is using this shortened version, the first motion effect needs to have a `type` of `UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis` and not `UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis` as it is in the code laid out – Flexicoder Jul 13 '14 at 11:47
  • 1
    @BooHoo Adding them as a group is obsolete? Where do you see that? As of the time of this writing the Apple docs don't have anything about it being obsolete. The whole purpose of grouping them is to improve performance. All effects in the group are rendered once. When you apply them separately, they must be rendered separately. Feel free to correct me if I'm wrong (and point me to Apple's documentation on it.) – David Lari Apr 19 '16 at 18:19
6
const static CGFloat kCustomIOS7MotionEffectExtent = 10.0; 

- (void)applyMotionEffects:(UIView *YOUR_VIEW) {
     if (NSClassFromString(@"UIInterpolatingMotionEffect")) {
         UIInterpolatingMotionEffect *horizontalEffect = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.x"
                                                                                                        type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
         horizontalEffect.minimumRelativeValue = @(-kCustomIOS7MotionEffectExtent);
         horizontalEffect.maximumRelativeValue = @( kCustomIOS7MotionEffectExtent);
         UIInterpolatingMotionEffect *verticalEffect = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.y"
                                                                                                      type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];
         verticalEffect.minimumRelativeValue = @(-kCustomIOS7MotionEffectExtent);
         verticalEffect.maximumRelativeValue = @( kCustomIOS7MotionEffectExtent);
         UIMotionEffectGroup *motionEffectGroup = [[UIMotionEffectGroup alloc] init];
         motionEffectGroup.motionEffects = @[horizontalEffect, verticalEffect]; 
         [YOUR_VIEW addMotionEffect:motionEffectGroup];
     }
}
damithH
  • 5,148
  • 2
  • 27
  • 31
5

Here is an easy category to integrate the effect on iOs7+ :

NSString *const centerX = @"center.x";
NSString *const centerY = @"center.y";

//#define IS_OS_7_OR_LATER    ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0)

@implementation UIView (TLMotionEffect)

- (void)addCenterMotionEffectsXYWithOffset:(CGFloat)offset {

//    if(!IS_OS_7_OR_LATER) return;
    if(floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1) return;

    UIInterpolatingMotionEffect *xAxis;
    UIInterpolatingMotionEffect *yAxis;

    xAxis = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:centerX type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
    xAxis.maximumRelativeValue = @(offset);
    xAxis.minimumRelativeValue = @(-offset);

    yAxis = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:centerY type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];
    yAxis.minimumRelativeValue = @(-offset);
    yAxis.maximumRelativeValue = @(offset);

    UIMotionEffectGroup *group = [[UIMotionEffectGroup alloc] init];
    group.motionEffects = @[xAxis, yAxis];

    [self addMotionEffect:group];
}

@end

https://github.com/jvenegas/TLMotionEffect

Maciej Swic
  • 11,139
  • 8
  • 52
  • 68
Bejil
  • 412
  • 5
  • 18
  • 1
    While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. – Maciej Swic Dec 29 '14 at 09:55
  • It will be hard to integrate the whole github project here – Bejil Dec 29 '14 at 12:41
  • 1
    You don't have to bring the whole project, only the essential part like i demonstrated by editing the answer. This is so that the code sticks around in case Github is down, you pull the repo or the sky falls down. – Maciej Swic Dec 29 '14 at 13:49
4

UIMotionEffect provides a free parallax implementation on iOS 7.

http://www.teehanlax.com/blog/introduction-to-uimotioneffect/

https://github.com/michaeljbishop/NGAParallaxMotion lets you just set the parallax intensity.

Dan Fabulich
  • 37,506
  • 41
  • 139
  • 175
3

This will help someone who is looking to implement parallax for tableView or collectionView.

  • first of all create the cell for the tableview and put the image view in it.

  • set the image height slightly more than the cell height. if cell height = 160 let the image height be 200 (to make the parallax effect and you can change it accordingly)

  • put this two variable in your viewController or any class where your tableView delegate is extended

let imageHeight:CGFloat = 150.0
let OffsetSpeed: CGFloat = 25.0
  • add the following code in the same class
 func scrollViewDidScroll(scrollView: UIScrollView) {
    //  print("inside scroll")

    if let visibleCells = seriesTabelView.visibleCells as? [SeriesTableViewCell] {
        for parallaxCell in visibleCells {
            var yOffset = ((seriesTabelView.contentOffset.y - parallaxCell.frame.origin.y) / imageHeight) * OffsetSpeedTwo
            parallaxCell.offset(CGPointMake(0.0, yOffset))
        }
    }
}
  • where seriesTabelView is my UItableview

  • and now lets goto the cell of this tableView and add the following code

func offset(offset: CGPoint) {
        posterImage.frame = CGRectOffset(self.posterImage.bounds, offset.x, offset.y)
    }
  • were posterImage is my UIImageView

If you want to implement this to collectionView just change the tableView vairable to your collectionView variable

and thats it. i am not sure if this is the best way. but it works for me. hope it works for you too. and let me know if there is any problem

Muneef M
  • 1,094
  • 15
  • 17
2

Really dam easy to do so why refers complex code. just try it. Working

Take a view on imageview exactly size of image view. by default alpha for view set 0.

//MARK: Scroll View Delegate methods
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{

NSLog(@"X: %f Y: %f",scrollView.contentOffset.x,scrollView.contentOffset.y);

CGFloat scrollY = _mainScrollView.contentOffset.y;
CGFloat height = _alphaView.frame.size.height;

CGFloat alphaMonitor = scrollY/height;

_alphaView.alpha = alphaMonitor;
}
Mr.Javed Multani
  • 12,549
  • 4
  • 53
  • 52
-1

Swift translation:

// Set vertical effect
let verticalMotionEffect = UIInterpolatingMotionEffect(keyPath: "center.y", type: .TiltAlongVerticalAxis)
verticalMotionEffect.minimumRelativeValue = -value
verticalMotionEffect.maximumRelativeValue = value

// Set vertical effect
let horizontalMotionEffect = UIInterpolatingMotionEffect(keyPath: "center.x", type: .TiltAlongHorizontalAxis)
verticalMotionEffect.minimumRelativeValue = -value
verticalMotionEffect.maximumRelativeValue = value

let group = UIMotionEffectGroup()
group.motionEffects = [horizontalMotionEffect, verticalMotionEffect]
self.motionEffect = group
self.addMotionEffect(group)
mukaissi
  • 2,441
  • 1
  • 21
  • 12