Skip to content
6 changes: 6 additions & 0 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ rearrange the view, using the layout we extracted from our original two views.
4. Title (for UILabel)
5. Text or placeholder (for UITextField)
6. Class
- If you wish a view to be ignored by TPMultiLayoutViewController, assign it a negative tag.
- You can register view classes TPMultiLayoutViewController should not descend into by calling

[TPMultiLayoutViewContoller registerViewClassToIgnore:*yourclass*];

A good place to do this is in your class' -(void)initialize method.

If you experience odd behaviour, check the log for "Couldn't find match..." messages. If a view cannot be matched to its counterpart, try setting the same tag for both views.

Expand Down
15 changes: 5 additions & 10 deletions TPMultiLayoutViewController.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,13 @@

#import <UIKit/UIKit.h>

@interface TPMultiLayoutViewController : UIViewController {
UIView *portraitView;
UIView *landscapeView;

@private
NSDictionary *portraitAttributes;
NSDictionary *landscapeAttributes;
BOOL viewIsCurrentlyPortrait;
}
@interface TPMultiLayoutViewController : UIViewController

// Call directly to use with custom animation (override willRotateToInterfaceOrientation to disable the switch there)
- (void)applyLayoutForInterfaceOrientation:(UIInterfaceOrientation)newOrientation;
- (void)applyLayoutForInterfaceOrientation:(UIInterfaceOrientation)newOrientation duration:(NSTimeInterval)duration;

// Call this with the class of custom views you do not wish TPMultiLayoutViewController to descend into.
+(void)registerViewClassToIgnore:(Class)viewClass;

@property (nonatomic, retain) IBOutlet UIView *landscapeView;
@property (nonatomic, retain) IBOutlet UIView *portraitView;
Expand Down
129 changes: 69 additions & 60 deletions TPMultiLayoutViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -13,68 +13,78 @@ @interface TPMultiLayoutViewController ()
- (NSDictionary*)attributeTableForViewHierarchy:(UIView*)rootView associateWithViewHierarchy:(UIView*)associatedRootView;
- (void)addAttributesForSubviewHierarchy:(UIView*)view associatedWithSubviewHierarchy:(UIView*)associatedView toTable:(NSMutableDictionary*)table;
- (UIView*)findAssociatedViewForView:(UIView*)view amongViews:(NSArray*)views;
- (void)applyAttributeTable:(NSDictionary*)table toViewHierarchy:(UIView*)view;
- (void)applyAttributeTable:(NSDictionary*)table toViewHierarchy:(UIView*)view duration:(NSTimeInterval)duration;
- (NSDictionary*)attributesForView:(UIView*)view;
- (void)applyAttributes:(NSDictionary*)attributes toView:(UIView*)view;
- (void)applyAttributes:(NSDictionary*)attributes toView:(UIView*)view duration:(NSTimeInterval)duration;
- (BOOL)shouldDescendIntoSubviewsOfView:(UIView*)view;

@property (nonatomic, strong) NSDictionary *portraitAttributes;
@property (nonatomic, strong) NSDictionary *landscapeAttributes;
@property (nonatomic, assign) BOOL viewIsCurrentlyPortrait;

@end

static NSMutableSet* sViewClassesToIgnore = nil;

@implementation TPMultiLayoutViewController
@synthesize portraitView, landscapeView;

#pragma mark - View lifecycle

- (void)viewDidLoad {
[super viewDidLoad];

// Construct attribute tables
portraitAttributes = [[self attributeTableForViewHierarchy:portraitView associateWithViewHierarchy:self.view] retain];
landscapeAttributes = [[self attributeTableForViewHierarchy:landscapeView associateWithViewHierarchy:self.view] retain];
viewIsCurrentlyPortrait = (self.view == portraitView);

// Don't need to retain the original template view hierarchies any more
self.portraitView = nil;
self.landscapeView = nil;
+(void)registerViewClassToIgnore:(Class)viewClass
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sViewClassesToIgnore = [[NSMutableSet alloc] init];
});

[sViewClassesToIgnore addObject:viewClass];
}

- (void)viewDidUnload {
[super viewDidUnload];

[portraitAttributes release];
portraitAttributes = nil;
[landscapeAttributes release];
landscapeAttributes = nil;
+(void)initialize
{
[self registerViewClassToIgnore:[UISlider class]];
[self registerViewClassToIgnore:[UISwitch class]];
[self registerViewClassToIgnore:[UITextField class]];
[self registerViewClassToIgnore:[UIWebView class]];
[self registerViewClassToIgnore:[UITableView class]];
[self registerViewClassToIgnore:[UIPickerView class]];
[self registerViewClassToIgnore:[UIDatePicker class]];
[self registerViewClassToIgnore:[UITextView class]];
[self registerViewClassToIgnore:[UIProgressView class]];
[self registerViewClassToIgnore:[UISegmentedControl class]];

[super initialize];
}

- (void)dealloc {
[portraitAttributes release];
portraitAttributes = nil;
[landscapeAttributes release];
landscapeAttributes = nil;
- (void)viewDidLoad {
[super viewDidLoad];

[super dealloc];
// Construct attribute tables
self.portraitAttributes = [self attributeTableForViewHierarchy:self.portraitView associateWithViewHierarchy:self.view];
self.landscapeAttributes = [self attributeTableForViewHierarchy:self.landscapeView associateWithViewHierarchy:self.view];
self.viewIsCurrentlyPortrait = (self.view == self.portraitView);
}

-(void)viewWillAppear:(BOOL)animated {
// Display correct layout for orientation
if ( (UIInterfaceOrientationIsPortrait(self.interfaceOrientation) && !viewIsCurrentlyPortrait) ||
(UIInterfaceOrientationIsLandscape(self.interfaceOrientation) && viewIsCurrentlyPortrait) ) {
[self applyLayoutForInterfaceOrientation:self.interfaceOrientation];
if ( (UIInterfaceOrientationIsPortrait(self.interfaceOrientation) && !self.viewIsCurrentlyPortrait) ||
(UIInterfaceOrientationIsLandscape(self.interfaceOrientation) && self.viewIsCurrentlyPortrait) ) {
[self applyLayoutForInterfaceOrientation:self.interfaceOrientation duration:0];
}
}

#pragma mark - Rotation

- (void)applyLayoutForInterfaceOrientation:(UIInterfaceOrientation)newOrientation {
NSDictionary *table = UIInterfaceOrientationIsPortrait(newOrientation) ? portraitAttributes : landscapeAttributes;
[self applyAttributeTable:table toViewHierarchy:self.view];
viewIsCurrentlyPortrait = UIInterfaceOrientationIsPortrait(newOrientation);
- (void)applyLayoutForInterfaceOrientation:(UIInterfaceOrientation)newOrientation duration:(NSTimeInterval)duration {
NSDictionary *table = UIInterfaceOrientationIsPortrait(newOrientation) ? self.portraitAttributes : self.landscapeAttributes;
[self applyAttributeTable:table toViewHierarchy:self.view duration:duration];
self.viewIsCurrentlyPortrait = UIInterfaceOrientationIsPortrait(newOrientation);
}

-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
if ( (UIInterfaceOrientationIsPortrait(toInterfaceOrientation) && !viewIsCurrentlyPortrait) ||
(UIInterfaceOrientationIsLandscape(toInterfaceOrientation) && viewIsCurrentlyPortrait) ) {
[self applyLayoutForInterfaceOrientation:toInterfaceOrientation];
if ( (UIInterfaceOrientationIsPortrait(toInterfaceOrientation) && !self.viewIsCurrentlyPortrait) ||
(UIInterfaceOrientationIsLandscape(toInterfaceOrientation) && self.viewIsCurrentlyPortrait) ) {
[self applyLayoutForInterfaceOrientation:toInterfaceOrientation duration:duration];
}
}

Expand All @@ -87,7 +97,12 @@ - (NSDictionary*)attributeTableForViewHierarchy:(UIView*)rootView associateWithV
}

- (void)addAttributesForSubviewHierarchy:(UIView*)view associatedWithSubviewHierarchy:(UIView*)associatedView toTable:(NSMutableDictionary*)table {
[table setObject:[self attributesForView:view] forKey:[NSValue valueWithPointer:associatedView]];
// Ignore views with negative tag
if ( view.tag < 0 ) {
return;
}

[table setObject:[self attributesForView:view] forKey:[NSValue valueWithPointer:(__bridge const void *)(associatedView)]];

if ( ![self shouldDescendIntoSubviewsOfView:view] ) return;

Expand All @@ -100,7 +115,7 @@ - (void)addAttributesForSubviewHierarchy:(UIView*)view associatedWithSubviewHier
}

- (UIView*)findAssociatedViewForView:(UIView*)view amongViews:(NSArray*)views {
// First try to match tag
// First try to match tag
if ( view.tag != 0 ) {
UIView *associatedView = [[views filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"tag = %d", view.tag]] lastObject];
if ( associatedView ) return associatedView;
Expand All @@ -117,7 +132,7 @@ - (UIView*)findAssociatedViewForView:(UIView*)view amongViews:(NSArray*)views {
UIControlEvents controlEvents = [(UIControl*)otherView allControlEvents];
for ( id target in [(UIControl*)otherView allTargets] ) {
// Iterate over each bit in the UIControlEvents bitfield
for ( NSInteger i=0; i<sizeof(UIControlEvents)*8; i++ ) {
for ( NSInteger i=0; i<(NSInteger)sizeof(UIControlEvents)*8; i++ ) {
UIControlEvents event = 1 << i;
if ( !(controlEvents & event) ) continue;
if ( ![[(UIControl*)otherView actionsForTarget:target forControlEvent:event] isEqualToArray:[(UIControl*)view actionsForTarget:target forControlEvent:event]] ) {
Expand Down Expand Up @@ -186,18 +201,18 @@ - (UIView*)findAssociatedViewForView:(UIView*)view amongViews:(NSArray*)views {
return nil;
}

- (void)applyAttributeTable:(NSDictionary*)table toViewHierarchy:(UIView*)view {
NSDictionary *attributes = [table objectForKey:[NSValue valueWithPointer:view]];
- (void)applyAttributeTable:(NSDictionary*)table toViewHierarchy:(UIView*)view duration:(NSTimeInterval)duration {
NSDictionary *attributes = [table objectForKey:[NSValue valueWithPointer:(__bridge const void *)(view)]];
if ( attributes ) {
[self applyAttributes:attributes toView:view];
[self applyAttributes:attributes toView:view duration:duration];
}

if ( view.hidden ) return;

if ( ![self shouldDescendIntoSubviewsOfView:view] ) return;

for ( UIView *subview in view.subviews ) {
[self applyAttributeTable:table toViewHierarchy:subview];
[self applyAttributeTable:table toViewHierarchy:subview duration:duration];
}
}

Expand All @@ -212,25 +227,19 @@ - (NSDictionary*)attributesForView:(UIView*)view {
return attributes;
}

- (void)applyAttributes:(NSDictionary*)attributes toView:(UIView*)view {
view.frame = [[attributes objectForKey:@"frame"] CGRectValue];
view.bounds = [[attributes objectForKey:@"bounds"] CGRectValue];
view.hidden = [[attributes objectForKey:@"hidden"] boolValue];
view.autoresizingMask = [[attributes objectForKey:@"autoresizingMask"] integerValue];
- (void)applyAttributes:(NSDictionary*)attributes toView:(UIView*)view duration:(NSTimeInterval)duration {
[UIView animateWithDuration:duration
animations:^{
view.frame = [[attributes objectForKey:@"frame"] CGRectValue];
view.bounds = [[attributes objectForKey:@"bounds"] CGRectValue];
view.hidden = [[attributes objectForKey:@"hidden"] boolValue];
view.autoresizingMask = [[attributes objectForKey:@"autoresizingMask"] integerValue];
}];
}

- (BOOL)shouldDescendIntoSubviewsOfView:(UIView*)view {
if ( [view isKindOfClass:[UISlider class]] ||
[view isKindOfClass:[UISwitch class]] ||
[view isKindOfClass:[UITextField class]] ||
[view isKindOfClass:[UIWebView class]] ||
[view isKindOfClass:[UITableView class]] ||
[view isKindOfClass:[UIPickerView class]] ||
[view isKindOfClass:[UIDatePicker class]] ||
[view isKindOfClass:[UITextView class]] ||
[view isKindOfClass:[UIProgressView class]] ||
[view isKindOfClass:[UISegmentedControl class]] ) return NO;
if ([sViewClassesToIgnore containsObject:[view class]]) return NO;
return YES;
}

@end
@end