Skip to content

Instantly share code, notes, and snippets.

@stevesparks
Last active August 29, 2015 14:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stevesparks/208ee30bba2b163c63cc to your computer and use it in GitHub Desktop.
Save stevesparks/208ee30bba2b163c63cc to your computer and use it in GitHub Desktop.
Autolayout Gists
//
// RotationResistantView.h
//
// Created by Steve Sparks on 1/24/15.
//
#import <UIKit/UIKit.h>
typedef NS_ENUM(NSInteger, RRLayoutAttribute) {
RRLayoutAttributeLeft = 1,
RRLayoutAttributeRight,
RRLayoutAttributeTop,
RRLayoutAttributeBottom,
RRLayoutAttributeLeading,
RRLayoutAttributeTrailing,
RRLayoutAttributeWidth,
RRLayoutAttributeHeight,
RRLayoutAttributeCenterX,
RRLayoutAttributeCenterY,
RRLayoutAttributeBaseline,
RRLayoutAttributeLastBaseline = RRLayoutAttributeBaseline,
RRLayoutAttributeFirstBaseline NS_ENUM_AVAILABLE_IOS(8_0),
RRLayoutAttributeLeftMargin NS_ENUM_AVAILABLE_IOS(8_0),
RRLayoutAttributeRightMargin NS_ENUM_AVAILABLE_IOS(8_0),
RRLayoutAttributeTopMargin NS_ENUM_AVAILABLE_IOS(8_0),
RRLayoutAttributeBottomMargin NS_ENUM_AVAILABLE_IOS(8_0),
RRLayoutAttributeLeadingMargin NS_ENUM_AVAILABLE_IOS(8_0),
RRLayoutAttributeTrailingMargin NS_ENUM_AVAILABLE_IOS(8_0),
RRLayoutAttributeCenterXWithinMargins NS_ENUM_AVAILABLE_IOS(8_0),
RRLayoutAttributeCenterYWithinMargins NS_ENUM_AVAILABLE_IOS(8_0),
RRLayoutAttributeNotAnAttribute = 0
};
#define RRReversedAttr @{\
@(NSLayoutAttributeNotAnAttribute) : @(NSLayoutAttributeNotAnAttribute),\
@(NSLayoutAttributeLeft) : @(NSLayoutAttributeRight),\
@(NSLayoutAttributeRight) : @(NSLayoutAttributeLeft),\
@(NSLayoutAttributeTop) : @(NSLayoutAttributeBottom),\
@(NSLayoutAttributeBottom) : @(NSLayoutAttributeTop),\
@(NSLayoutAttributeLeading) : @(NSLayoutAttributeTrailing),\
@(NSLayoutAttributeTrailing) : @(NSLayoutAttributeLeading),\
@(NSLayoutAttributeWidth) : @(NSLayoutAttributeWidth),\
@(NSLayoutAttributeHeight) : @(NSLayoutAttributeHeight),\
@(NSLayoutAttributeCenterX) : @(NSLayoutAttributeCenterX),\
@(NSLayoutAttributeCenterY) : @(NSLayoutAttributeCenterY),\
@(NSLayoutAttributeBaseline) : @(NSLayoutAttributeFirstBaseline),\
@(NSLayoutAttributeLastBaseline) : @(NSLayoutAttributeFirstBaseline),\
@(NSLayoutAttributeFirstBaseline) : @(NSLayoutAttributeLastBaseline),\
@(NSLayoutAttributeLeftMargin) : @(NSLayoutAttributeRightMargin),\
@(NSLayoutAttributeRightMargin) : @(NSLayoutAttributeLeftMargin),\
@(NSLayoutAttributeTopMargin) : @(NSLayoutAttributeBottomMargin),\
@(NSLayoutAttributeBottomMargin) : @(NSLayoutAttributeTopMargin),\
@(NSLayoutAttributeLeadingMargin) : @(NSLayoutAttributeTrailingMargin),\
@(NSLayoutAttributeTrailingMargin) : @(NSLayoutAttributeLeadingMargin),\
@(NSLayoutAttributeCenterXWithinMargins) : @(NSLayoutAttributeCenterXWithinMargins),\
@(NSLayoutAttributeCenterYWithinMargins) : @(NSLayoutAttributeCenterYWithinMargins),\
}
#define RRClockwiseAttr @{\
@(NSLayoutAttributeNotAnAttribute) : @(NSLayoutAttributeNotAnAttribute),\
@(NSLayoutAttributeLeft) : @(NSLayoutAttributeTop),\
@(NSLayoutAttributeTop) : @(NSLayoutAttributeRight),\
@(NSLayoutAttributeRight) : @(NSLayoutAttributeBottom),\
@(NSLayoutAttributeBottom) : @(NSLayoutAttributeLeft),\
@(NSLayoutAttributeLeading) : @(NSLayoutAttributeTop),\
@(NSLayoutAttributeTrailing) : @(NSLayoutAttributeBottom),\
@(NSLayoutAttributeWidth) : @(NSLayoutAttributeHeight),\
@(NSLayoutAttributeHeight) : @(NSLayoutAttributeWidth),\
@(NSLayoutAttributeCenterX) : @(NSLayoutAttributeCenterY),\
@(NSLayoutAttributeCenterY) : @(NSLayoutAttributeCenterX),\
@(NSLayoutAttributeBaseline) : @(NSLayoutAttributeFirstBaseline),\
@(NSLayoutAttributeLastBaseline) : @(NSLayoutAttributeFirstBaseline),\
@(NSLayoutAttributeFirstBaseline) : @(NSLayoutAttributeLastBaseline),\
@(NSLayoutAttributeLeftMargin) : @(NSLayoutAttributeTopMargin),\
@(NSLayoutAttributeTopMargin) : @(NSLayoutAttributeRightMargin),\
@(NSLayoutAttributeRightMargin) : @(NSLayoutAttributeBottomMargin),\
@(NSLayoutAttributeBottomMargin) : @(NSLayoutAttributeLeftMargin),\
@(NSLayoutAttributeLeadingMargin) : @(NSLayoutAttributeTrailingMargin),\
@(NSLayoutAttributeTrailingMargin) : @(NSLayoutAttributeLeadingMargin),\
@(NSLayoutAttributeCenterXWithinMargins) : @(NSLayoutAttributeCenterYWithinMargins),\
@(NSLayoutAttributeCenterYWithinMargins) : @(NSLayoutAttributeCenterXWithinMargins),\
}
#define RRCounterclockwiseAttr @{\
@(NSLayoutAttributeNotAnAttribute) : @(NSLayoutAttributeNotAnAttribute),\
@(NSLayoutAttributeLeft) : @(NSLayoutAttributeBottom),\
@(NSLayoutAttributeBottom) : @(NSLayoutAttributeRight),\
@(NSLayoutAttributeRight) : @(NSLayoutAttributeTop),\
@(NSLayoutAttributeTop) : @(NSLayoutAttributeLeft),\
@(NSLayoutAttributeLeading) : @(NSLayoutAttributeBottom),\
@(NSLayoutAttributeTrailing) : @(NSLayoutAttributeTop),\
@(NSLayoutAttributeWidth) : @(NSLayoutAttributeHeight),\
@(NSLayoutAttributeHeight) : @(NSLayoutAttributeWidth),\
@(NSLayoutAttributeCenterX) : @(NSLayoutAttributeCenterY),\
@(NSLayoutAttributeCenterY) : @(NSLayoutAttributeCenterX),\
@(NSLayoutAttributeBaseline) : @(NSLayoutAttributeFirstBaseline),\
@(NSLayoutAttributeLastBaseline) : @(NSLayoutAttributeFirstBaseline),\
@(NSLayoutAttributeFirstBaseline) : @(NSLayoutAttributeLastBaseline),\
@(NSLayoutAttributeLeftMargin) : @(NSLayoutAttributeBottomMargin),\
@(NSLayoutAttributeTopMargin) : @(NSLayoutAttributeLeftMargin),\
@(NSLayoutAttributeRightMargin) : @(NSLayoutAttributeTopMargin),\
@(NSLayoutAttributeBottomMargin) : @(NSLayoutAttributeRightMargin),\
@(NSLayoutAttributeLeadingMargin) : @(NSLayoutAttributeTopMargin),\
@(NSLayoutAttributeTrailingMargin) : @(NSLayoutAttributeBottomMargin),\
@(NSLayoutAttributeCenterXWithinMargins) : @(NSLayoutAttributeCenterYWithinMargins),\
@(NSLayoutAttributeCenterYWithinMargins) : @(NSLayoutAttributeCenterXWithinMargins),\
}
#define RRReversedConstantMult @{\
@(NSLayoutAttributeNotAnAttribute) : @(0),\
@(NSLayoutAttributeLeft) : @(-1),\
@(NSLayoutAttributeRight) : @(-1),\
@(NSLayoutAttributeTop) : @(-1),\
@(NSLayoutAttributeBottom) : @(-1),\
@(NSLayoutAttributeLeading) : @(-1),\
@(NSLayoutAttributeTrailing) : @(-1),\
@(NSLayoutAttributeWidth) : @(1),\
@(NSLayoutAttributeHeight) : @(1),\
@(NSLayoutAttributeCenterX) : @(-1),\
@(NSLayoutAttributeCenterY) : @(-1),\
@(NSLayoutAttributeBaseline) : @(-1),\
@(NSLayoutAttributeLastBaseline) : @(-1),\
@(NSLayoutAttributeFirstBaseline) : @(-1),\
@(NSLayoutAttributeLeftMargin) : @(-1),\
@(NSLayoutAttributeRightMargin) : @(-1),\
@(NSLayoutAttributeTopMargin) : @(-1),\
@(NSLayoutAttributeBottomMargin) : @(-1),\
@(NSLayoutAttributeLeadingMargin) : @(-1),\
@(NSLayoutAttributeTrailingMargin) : @(-1),\
@(NSLayoutAttributeCenterXWithinMargins) : @(-1),\
@(NSLayoutAttributeCenterYWithinMargins) : @(-1),\
}
#define RRClockwiseConstantMult @{\
@(NSLayoutAttributeNotAnAttribute) : @(0),\
@(NSLayoutAttributeLeft) : @(1),\
@(NSLayoutAttributeRight) : @(1),\
@(NSLayoutAttributeTop) : @(-1),\
@(NSLayoutAttributeBottom) : @(-1),\
@(NSLayoutAttributeLeading) : @(1),\
@(NSLayoutAttributeTrailing) : @(1),\
@(NSLayoutAttributeWidth) : @(1),\
@(NSLayoutAttributeHeight) : @(1),\
@(NSLayoutAttributeCenterX) : @(-1),\
@(NSLayoutAttributeCenterY) : @(1),\
@(NSLayoutAttributeBaseline) : @(-1),\
@(NSLayoutAttributeLastBaseline) : @(-1),\
@(NSLayoutAttributeFirstBaseline) : @(-1),\
@(NSLayoutAttributeLeftMargin) : @(1),\
@(NSLayoutAttributeRightMargin) : @(1),\
@(NSLayoutAttributeTopMargin) : @(-1),\
@(NSLayoutAttributeBottomMargin) : @(-1),\
@(NSLayoutAttributeLeadingMargin) : @(1),\
@(NSLayoutAttributeTrailingMargin) : @(1),\
@(NSLayoutAttributeCenterXWithinMargins) : @(-1),\
@(NSLayoutAttributeCenterYWithinMargins) : @(1)\
}
#define RRCounterclockwiseConstantMult @{\
@(NSLayoutAttributeNotAnAttribute) : @(0),\
@(NSLayoutAttributeLeft) : @(-1),\
@(NSLayoutAttributeRight) : @(-1),\
@(NSLayoutAttributeTop) : @(1),\
@(NSLayoutAttributeBottom) : @(1),\
@(NSLayoutAttributeLeading) : @(-1),\
@(NSLayoutAttributeTrailing) : @(-1),\
@(NSLayoutAttributeWidth) : @(1),\
@(NSLayoutAttributeHeight) : @(1),\
@(NSLayoutAttributeCenterX) : @(-1),\
@(NSLayoutAttributeCenterY) : @(1),\
@(NSLayoutAttributeBaseline) : @(-1),\
@(NSLayoutAttributeLastBaseline) : @(-1),\
@(NSLayoutAttributeFirstBaseline) : @(-1),\
@(NSLayoutAttributeLeftMargin) : @(-1),\
@(NSLayoutAttributeRightMargin) : @(-1),\
@(NSLayoutAttributeTopMargin) : @(1),\
@(NSLayoutAttributeBottomMargin) : @(1),\
@(NSLayoutAttributeLeadingMargin) : @(-1),\
@(NSLayoutAttributeTrailingMargin) : @(-1),\
@(NSLayoutAttributeCenterXWithinMargins) : @(-1),\
@(NSLayoutAttributeCenterYWithinMargins) : @(1),\
}
@interface RotationResistantView : UIView
- (void)rr_preserveConstraints;
@end
//
// RotationResistantView.m
//
// Created by Steve Sparks on 1/24/15.
//
#import "RotationResistantView.h"
#import "Pannr.h"
@interface RRLayoutConstraint : NSObject
@property (nonatomic) id firstItem;
@property (nonatomic) RRLayoutAttribute firstAttribute;
@property (nonatomic) NSLayoutRelation relation;
@property (nonatomic) id secondItem;
@property (nonatomic) RRLayoutAttribute secondAttribute;
@property (nonatomic) CGFloat multiplier;
@property (nonatomic) CGFloat constant;
@property (weak, nonatomic) NSLayoutConstraint *currentConstraint;
- (instancetype) initWithConstraint:(NSLayoutConstraint*)constraint inOrientation:(UIDeviceOrientation)orientation;
@end
@implementation RRLayoutConstraint
- (instancetype)initWithConstraint:(NSLayoutConstraint *)constraint inOrientation:(UIDeviceOrientation)orientation {
self = [super init];
if(self) {
self.currentConstraint = constraint;
self.firstItem = constraint.firstItem;
self.secondItem = constraint.secondItem;
self.constant = constraint.constant;
self.multiplier = constraint.multiplier;
self.relation = constraint.relation;
// We need to save constraints relative to portrait, so right here,
// we unroll whatever orientation they have applied, and see what
// the constraints are w/r/t portrait.
switch([UIDevice currentDevice].orientation) {
case UIDeviceOrientationLandscapeLeft:
self.firstAttribute = [RRClockwiseAttr[@(constraint.firstAttribute)] integerValue];
self.secondAttribute = [RRClockwiseAttr[@(constraint.secondAttribute)] integerValue];
break;
case UIDeviceOrientationLandscapeRight:
self.firstAttribute = [RRCounterclockwiseAttr[@(constraint.firstAttribute)] integerValue];
self.secondAttribute = [RRCounterclockwiseAttr[@(constraint.secondAttribute)] integerValue];
break;
case UIDeviceOrientationPortraitUpsideDown:
self.firstAttribute = [RRReversedAttr[@(constraint.firstAttribute)] integerValue];
self.secondAttribute = [RRReversedAttr[@(constraint.secondAttribute)] integerValue];
break;
default:
self.firstAttribute = (RRLayoutAttribute)constraint.firstAttribute;
self.secondAttribute = (RRLayoutAttribute)constraint.secondAttribute;
break;
}
}
return self;
}
- (NSLayoutConstraint *) constraintForOrientation:(UIDeviceOrientation)orientation {
NSLayoutAttribute newFirst = (NSLayoutAttribute)self.firstAttribute;
NSLayoutAttribute newSecond = (NSLayoutAttribute)self.secondAttribute;
CGFloat newConstant = self.constant;
switch(orientation) {
case UIDeviceOrientationLandscapeLeft:
newFirst = [RRCounterclockwiseAttr[@(self.firstAttribute)] integerValue];
newSecond = [RRCounterclockwiseAttr[@(self.secondAttribute)] integerValue];
newConstant = newConstant * [RRCounterclockwiseConstantMult[@(self.firstAttribute)] doubleValue];
break;
case UIDeviceOrientationLandscapeRight:
newFirst = [RRClockwiseAttr[@(self.firstAttribute)] integerValue];
newSecond = [RRClockwiseAttr[@(self.secondAttribute)] integerValue];
newConstant = newConstant * [RRClockwiseConstantMult[@(self.firstAttribute)] doubleValue];
break;
case UIDeviceOrientationPortraitUpsideDown:
newConstant = newConstant * [RRReversedConstantMult[@(self.firstAttribute)] doubleValue];
case UIDeviceOrientationFaceDown:
case UIDeviceOrientationFaceUp:
case UIDeviceOrientationPortrait:
default:
;
}
if(newFirst && newSecond) {
return [NSLayoutConstraint constraintWithItem:self.firstItem attribute:newFirst relatedBy:self.relation toItem:self.secondItem attribute:newSecond multiplier:self.multiplier constant:newConstant];
}
return nil;
}
@end
@interface RotationResistantView ()
@property (nonatomic) NSArray *preservedConstraints;
@property (nonatomic) UIDeviceOrientation previousOrientation;
@end
@implementation RotationResistantView
- (void)layoutSubviews {
if(self.preservedConstraints.count) {
[self rr_removeAndApplyConstraints];
}
[super layoutSubviews];
}
- (void)rr_preserveConstraints {
NSMutableArray *newConstraints = [[NSMutableArray alloc] initWithCapacity:self.constraints.count];
UIDeviceOrientation o = [UIDevice currentDevice].orientation;
for(NSLayoutConstraint *c in self.constraints) {
if(c.secondAttribute != NSLayoutAttributeNotAnAttribute) {
RRLayoutConstraint *lc = [[RRLayoutConstraint alloc] initWithConstraint:c inOrientation:o];
[newConstraints addObject:lc];
}
}
self.preservedConstraints = [newConstraints copy];
}
- (void)rr_removeAndApplyConstraints {
UIDeviceOrientation o = [UIDevice currentDevice].orientation;
for(RRLayoutConstraint *localConstraint in self.preservedConstraints) {
NSLayoutConstraint *oldC = constraint.currentConstraint;
[self removeConstraint:oldC];
NSLayoutConstraint *newC = [localConstraint constraintForOrientation:o];
localConstraint.currentConstraint = newC;
[self addConstraint:newC];
}
}
@end
//
// UIViewController+RotationAware.h
//
// Created by Steve Sparks on 12/27/14.
//
#import <UIKit/UIKit.h>
@interface UIViewController(RotationAware)
- (void)ltc_addViewToRotationGroup:(UIView *)view;
- (void)ltc_removeViewFromRotationGroup:(UIView *)view;
- (CGAffineTransform)ltc_currentTransform;
- (void)ltc_updateOrientation;
@end
//
// UIViewController+RotationAware.m
//
// Created by Steve Sparks on 12/27/14.
//
#import "UIViewController+RotationAware.h"
#import <objc/runtime.h>
@implementation UIViewController(RotationAware)
const void *currentTransformKey = &currentTransformKey;
const void *rotationGroupKey = &rotationGroupKey;
- (NSMutableArray*)rotationGroup {
NSMutableArray *ret = objc_getAssociatedObject(self, rotationGroupKey);
if(!ret) {
ret = [[NSMutableArray alloc] init];
objc_setAssociatedObject(self, rotationGroupKey, ret, OBJC_ASSOCIATION_RETAIN);
}
return ret;
}
- (CGAffineTransform)ltc_currentTransform {
return [self currentTransform];
}
- (CGAffineTransform)currentTransform {
CGAffineTransform ret = CGAffineTransformIdentity;
NSValue *v = objc_getAssociatedObject(self, currentTransformKey);
if(v) {
ret = [v CGAffineTransformValue];
} else {
ret = [self transformForCurrentOrientation];
[self setCurrentTransform:ret];
}
return ret;
}
- (void)setCurrentTransform:(CGAffineTransform)currentTransform {
objc_setAssociatedObject(self, currentTransformKey, [NSValue valueWithCGAffineTransform:currentTransform], OBJC_ASSOCIATION_RETAIN);
}
#pragma mark -
- (void)ltc_updateOrientation {
[self noteOrientationChange:nil];
}
- (CGAffineTransform) transformForCurrentOrientation {
UIDeviceOrientation deviceOrientation = [[UIDevice currentDevice] orientation];
UIInterfaceOrientation statusBarOrientation = [[UIApplication sharedApplication] statusBarOrientation];
UIDeviceOrientation final = UIDeviceOrientationPortrait;
CGFloat angle = 0.0;
switch (statusBarOrientation) {
case UIInterfaceOrientationPortrait:
final = deviceOrientation;
break;
case UIInterfaceOrientationLandscapeLeft:
switch(deviceOrientation) {
case UIDeviceOrientationPortrait:
final = UIDeviceOrientationLandscapeRight;
break;
case UIDeviceOrientationLandscapeLeft:
final = UIDeviceOrientationPortrait;
break;
case UIDeviceOrientationLandscapeRight:
final = UIDeviceOrientationPortraitUpsideDown;
break;
case UIDeviceOrientationPortraitUpsideDown:
final = UIDeviceOrientationLandscapeRight;
break;
default:
;
}
break;
case UIInterfaceOrientationLandscapeRight:
switch(deviceOrientation) {
case UIDeviceOrientationPortrait:
final = UIDeviceOrientationLandscapeLeft;
break;
case UIDeviceOrientationLandscapeLeft:
final = UIDeviceOrientationPortraitUpsideDown;
break;
case UIDeviceOrientationLandscapeRight:
final = UIDeviceOrientationPortrait;
break;
case UIDeviceOrientationPortraitUpsideDown:
final = UIDeviceOrientationLandscapeRight;
break;
default:
;
}
break;
case UIInterfaceOrientationPortraitUpsideDown: {
switch(deviceOrientation) {
case UIDeviceOrientationPortrait:
final = UIDeviceOrientationPortraitUpsideDown;
break;
case UIDeviceOrientationLandscapeLeft:
final = UIDeviceOrientationLandscapeRight;
break;
case UIDeviceOrientationLandscapeRight:
final = UIDeviceOrientationLandscapeLeft;
break;
case UIDeviceOrientationPortraitUpsideDown:
final = UIDeviceOrientationPortrait;
break;
default:
;
}
break;
}
default:
break;
}
switch (final) {
case UIDeviceOrientationLandscapeLeft:
angle = M_PI_2;
break;
case UIDeviceOrientationLandscapeRight:
angle = 0 - M_PI_2;
break;
case UIDeviceOrientationPortraitUpsideDown:
angle = M_PI;
break;
default:
break;
}
CGAffineTransform transform = CGAffineTransformMakeRotation(angle);
return transform;
}
- (void)noteOrientationChange:(NSNotification*)note { // UIDeviceOrientationDidChangeNotification
CGAffineTransform transform = [self transformForCurrentOrientation];
self.currentTransform = transform;
[UIView animateWithDuration:0.25 animations:^{
for(UIView *view in self.rotationGroup) {
view.transform = transform;
}
}];
// [self willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)final duration:0.25];
}
#pragma mark -
- (void)ltc_addViewToRotationGroup:(UIView *)view {
if([view isKindOfClass:[UIView class]]) {
if(!self.rotationGroup.count) {
[self noteOrientationChange:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(noteOrientationChange:) name:UIDeviceOrientationDidChangeNotification object:nil];
}
view.transform = self.currentTransform;
[self.rotationGroup addObject:view];
}
}
- (void)ltc_removeViewFromRotationGroup:(UIView *)view {
[self.rotationGroup removeObject:view];
if(!self.rotationGroup.count) {
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
}
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment