Skip to content

Instantly share code, notes, and snippets.

@jessecurry
Created August 26, 2014 20:23
Show Gist options
  • Save jessecurry/e68bfd7f0a4708e680f4 to your computer and use it in GitHub Desktop.
Save jessecurry/e68bfd7f0a4708e680f4 to your computer and use it in GitHub Desktop.
Parallax NSImageView for OS X
//
// BFParallaxImageView.h
// bout-osx
//
// Created by Jesse Curry on 8/26/14.
// Copyright (c) 2014 Bout Fitness, LLC. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface BFParallaxImageView : NSView
- (void)setImage: (NSImage*)image;
@end
//
// BFParallaxImageView.m
// bout-osx
//
// Created by Jesse Curry on 8/26/14.
// Copyright (c) 2014 Bout Fitness, LLC. All rights reserved.
//
#import "BFParallaxImageView.h"
#define USE_POP_FOR_ANIMATION 1
#if USE_POP_FOR_ANIMATION
#import <pop/POP.h>
#endif
static const CGFloat kImageOverscan = 1.2; // 20% larger image
@interface BFParallaxImageView ()
@property (nonatomic, weak) NSTrackingArea* trackingArea;
@property (nonatomic, weak) NSImageView* imageView;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation BFParallaxImageView
- (instancetype)initWithFrame: (NSRect)frameRect
{
self = [super initWithFrame: frameRect];
if ( self )
{
[self setupView];
}
return self;
}
- (instancetype)initWithCoder: (NSCoder*)coder
{
self = [super initWithCoder: coder];
if ( self )
{
[self setupView];
}
return self;
}
- (void)setupView
{
self.wantsLayer = YES;
// Add Image View
NSImageView* imageView = [[NSImageView alloc] initWithFrame: self.bounds];
imageView.imageScaling = NSImageScaleProportionallyUpOrDown;
[self addSubview: imageView];
self.imageView = imageView;
// Start Tracking
NSTrackingAreaOptions options = (NSTrackingMouseMoved | NSTrackingActiveInActiveApp);
NSTrackingArea* trackingArea = [[NSTrackingArea alloc] initWithRect: self.bounds
options: options
owner: self
userInfo: nil];
[self addTrackingArea: trackingArea];
self.trackingArea = trackingArea;
}
#pragma mark -
- (void)setImage: (NSImage*)image
{
CGSize viewSize = self.bounds.size;
NSSize imageSize = image.size;
if ( imageSize.height > 0 && imageSize.width > 0 )
{
CGFloat wRatio = (viewSize.width / imageSize.width);
CGFloat hRatio = (viewSize.height / imageSize.height);
CGFloat scale = MAX(wRatio, hRatio);
scale *= kImageOverscan; // image should be 20% larger than this view to allow parallax
CGSize scaledSize = CGSizeMake(imageSize.width * scale, imageSize.height * scale);
CGFloat dX = (scaledSize.width - viewSize.width);
CGFloat dY = (scaledSize.height - viewSize.height);
self.imageView.frame = CGRectMake(-0.5 * dX, -0.5 * dY, scaledSize.width, scaledSize.height);
self.imageView.image = image;
}
else
{
self.imageView.bounds = CGRectMake(0, 0, viewSize.width, viewSize.height);
self.imageView.image = nil;
}
}
#pragma mark - Tracking
- (void)mouseMoved: (NSEvent*)theEvent
{
NSPoint eyeCenter = [self convertPoint: [theEvent locationInWindow]
fromView: nil];
CGSize viewSize = self.bounds.size;
CGSize imageSize = self.imageView.bounds.size;
CGFloat wR = MIN(1.0, (eyeCenter.x / viewSize.width));
CGFloat hR = MIN(1.0, (eyeCenter.y / viewSize.height));
CGFloat dX = (imageSize.width - viewSize.width);
CGFloat dY = (imageSize.height - viewSize.height);
CGFloat x = -(wR * dX);
CGFloat y = -(hR * dY);
#if USE_POP_FOR_ANIMATION
static NSString* const kPosAnimKey = @"positionAnimation";
POPSpringAnimation* posAnim = [self.imageView.layer pop_animationForKey: kPosAnimKey];
if ( posAnim == nil )
{
posAnim = [POPSpringAnimation animationWithPropertyNamed: kPOPLayerPosition];
posAnim.springBounciness = 10;
posAnim.springSpeed = 15;
posAnim.fromValue = [NSValue valueWithCGPoint: self.imageView.layer.position];
[self.imageView.layer pop_addAnimation: posAnim
forKey: kPosAnimKey];
}
posAnim.toValue = [NSValue valueWithCGPoint: CGPointMake(x, y)];
#else
self.imageView.layer.position = CGPointMake(x, y);
#endif
}
@end
@jessecurry
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment