Skip to content

Commit

Permalink
Fixed exception when delegate doesn't add the barButtonItem to a suit…
Browse files Browse the repository at this point in the history
…able bar, making the popover support now entirely optional (as it should be). Added the ability to put the Master _after_ the Detail instead of before it; works for both orientations, and includes an animated toggling action for convenience.
  • Loading branch information
mattgemmell committed Aug 3, 2010
1 parent 04b9bd5 commit 5dcd21b
Show file tree
Hide file tree
Showing 7 changed files with 217 additions and 40 deletions.
2 changes: 2 additions & 0 deletions Classes/DetailViewController.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
IBOutlet UIBarButtonItem *toggleItem;
IBOutlet UIBarButtonItem *verticalItem;
IBOutlet UIBarButtonItem *dividerStyleItem;
IBOutlet UIBarButtonItem *masterBeforeDetailItem;
UIPopoverController *popoverController;
UIToolbar *toolbar;

Expand All @@ -28,5 +29,6 @@
- (IBAction)toggleMasterView:(id)sender;
- (IBAction)toggleVertical:(id)sender;
- (IBAction)toggleDividerStyle:(id)sender;
- (IBAction)toggleMasterBeforeDetail:(id)sender;

@end
8 changes: 8 additions & 0 deletions Classes/DetailViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ - (void)configureView
toggleItem.title = ([splitController isShowingMaster]) ? @"Hide Master" : @"Show Master"; // "I... AM... THE MASTER!" Derek Jacobi. Gave me chills.
verticalItem.title = (splitController.vertical) ? @"Horizontal Split" : @"Vertical Split";
dividerStyleItem.title = (splitController.dividerStyle == MGSplitViewDividerStyleThin) ? @"Enable Dragging" : @"Disable Dragging";
masterBeforeDetailItem.title = (splitController.masterBeforeDetail) ? @"Detail First" : @"Master First";
}


Expand Down Expand Up @@ -147,6 +148,13 @@ - (IBAction)toggleDividerStyle:(id)sender
}


- (IBAction)toggleMasterBeforeDetail:(id)sender
{
[splitController toggleMasterBeforeDetail:sender];
[self configureView];
}


#pragma mark -
#pragma mark Rotation support

Expand Down
3 changes: 3 additions & 0 deletions Classes/MGSplitDividerView.m
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,9 @@ - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
CGPoint lastPt = [touch previousLocationInView:self];
CGPoint pt = [touch locationInView:self];
float offset = (splitViewController.vertical) ? pt.x - lastPt.x : pt.y - lastPt.y;
if (!splitViewController.masterBeforeDetail) {
offset = -offset;
}
splitViewController.splitPosition = splitViewController.splitPosition + offset;
}
}
Expand Down
14 changes: 13 additions & 1 deletion Classes/MGSplitViewController.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ typedef enum _MGSplitViewDividerStyle {
float _splitWidth;
id _delegate;
BOOL _vertical;
BOOL _masterBeforeDetail;
NSMutableArray *_viewControllers;
UIBarButtonItem *_barButtonItem; // To be compliant with wacky UISplitViewController behaviour.
UIPopoverController *_hiddenPopoverController; // Popover used to hold the master view if it's not always visible.
Expand All @@ -36,7 +37,8 @@ typedef enum _MGSplitViewDividerStyle {
@property (nonatomic, assign) BOOL showsMasterInPortrait; // applies to both portrait orientations (default NO)
@property (nonatomic, assign) BOOL showsMasterInLandscape; // applies to both landscape orientations (default YES)
@property (nonatomic, assign, getter=isVertical) BOOL vertical; // if NO, split is horizontal, i.e. master above detail (default YES)
@property (nonatomic, assign) float splitPosition; // starting position of split in pixels, relative to top or left (depending on .isVertical setting).
@property (nonatomic, assign, getter=isMasterBeforeDetail) BOOL masterBeforeDetail; // if NO, master view is below/right of detail (default YES)
@property (nonatomic, assign) float splitPosition; // starting position of split in pixels, relative to top/left (depending on .isVertical setting) if masterBeforeDetail is YES, else relative to bottom/right.
@property (nonatomic, assign) float splitWidth; // width of split in pixels.
@property (nonatomic, assign) BOOL allowsDraggingDivider; // whether to let the user drag the divider to alter the split position (default NO).

Expand All @@ -50,13 +52,23 @@ typedef enum _MGSplitViewDividerStyle {

// Actions
- (IBAction)toggleSplitOrientation:(id)sender; // toggles split axis between vertical (left/right; default) and horizontal (top/bottom).
- (IBAction)toggleMasterBeforeDetail:(id)sender; // toggles position of master view relative to detail view.
- (IBAction)toggleMasterView:(id)sender; // toggles display of the master view in the current orientation.
- (IBAction)showMasterPopover:(id)sender; // shows the master view in a popover spawned from the provided barButtonItem, if it's currently hidden.
- (void)notePopoverDismissed; // should rarely be needed, because you should not change the popover's delegate. If you must, then call this when it's dismissed.

// Conveniences for you, because I care.
- (BOOL)isShowingMaster;
- (void)setSplitPosition:(float)posn animated:(BOOL)animate; // Allows for animation of splitPosition changes. The property's regular setter is not animated.
/* Note: splitPosition is the width (in a left/right split, or height in a top/bottom split) of the master view.
It is relative to the appropriate side of the splitView, which can be any of the four sides depending on the values in isMasterBeforeDetail and isVertical:
isVertical = YES, isMasterBeforeDetail = YES: splitPosition is relative to the LEFT edge. (Default)
isVertical = YES, isMasterBeforeDetail = NO: splitPosition is relative to the RIGHT edge.
isVertical = NO, isMasterBeforeDetail = YES: splitPosition is relative to the TOP edge.
isVertical = NO, isMasterBeforeDetail = NO: splitPosition is relative to the BOTTOM edge.
This implementation was chosen so you don't need to recalculate equivalent splitPositions if the user toggles masterBeforeDetail themselves.
*/
- (void)setDividerStyle:(MGSplitViewDividerStyle)newStyle animated:(BOOL)animate; // Allows for animation of dividerStyle changes. The property's regular setter is not animated.
- (NSArray *)cornerViews;
/*
Expand Down
164 changes: 132 additions & 32 deletions Classes/MGSplitViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#define MG_MIN_VIEW_WIDTH 200.0 // minimum width a view is allowed to become as a result of changing the splitPosition.

#define MG_ANIMATION_CHANGE_SPLIT_ORIENTATION @"ChangeSplitOrientation" // Animation ID for internal use.
#define MG_ANIMATION_CHANGE_SUBVIEWS_ORDER @"ChangeSubviewsOrder" // Animation ID for internal use.


@interface MGSplitViewController (MGPrivateMethods)
Expand Down Expand Up @@ -127,6 +128,7 @@ - (void)setup
_showsMasterInLandscape = YES;
_reconfigurePopup = NO;
_vertical = YES;
_masterBeforeDetail = YES;
_splitPosition = MG_DEFAULT_SPLIT_POSITION;
CGRect divRect = self.view.bounds;
if ([self isVertical]) {
Expand Down Expand Up @@ -268,45 +270,72 @@ - (void)layoutSubviewsForInterfaceOrientation:(UIInterfaceOrientation)theOrienta
UIViewController *controller;
UIView *theView;
BOOL shouldShowMaster = [self shouldShowMasterForInterfaceOrientation:theOrientation];
BOOL masterFirst = [self isMasterBeforeDetail];
if ([self isVertical]) {
// Master on left, detail on right.
if (!shouldShowMaster) {
// Move off-screen.
newFrame.origin.x -= (_splitPosition + _splitWidth);
// Master on left, detail on right (or vice versa).
CGRect masterRect, dividerRect, detailRect;
if (masterFirst) {
if (!shouldShowMaster) {
// Move off-screen.
newFrame.origin.x -= (_splitPosition + _splitWidth);
}

newFrame.size.width = _splitPosition;
masterRect = newFrame;

newFrame.origin.x += newFrame.size.width;
newFrame.size.width = _splitWidth;
dividerRect = newFrame;

newFrame.origin.x += newFrame.size.width;
newFrame.size.width = width - newFrame.origin.x;
detailRect = newFrame;

} else {
if (!shouldShowMaster) {
// Move off-screen.
newFrame.size.width += (_splitPosition + _splitWidth);
}

newFrame.size.width -= (_splitPosition + _splitWidth);
detailRect = newFrame;

newFrame.origin.x += newFrame.size.width;
newFrame.size.width = _splitWidth;
dividerRect = newFrame;

newFrame.origin.x += newFrame.size.width;
newFrame.size.width = _splitPosition;
masterRect = newFrame;
}

// Position master.
controller = self.masterViewController;
if (controller && [controller isKindOfClass:[UIViewController class]]) {
theView = controller.view;
if (theView) {
newFrame.size.width = _splitPosition;
theView.frame = newFrame;
theView.frame = masterRect;
if (!theView.superview) {
[self.masterViewController viewWillAppear:NO];
[controller viewWillAppear:NO];
[self.view addSubview:theView];
[self.masterViewController viewDidAppear:NO];
[controller viewDidAppear:NO];
}
newFrame.origin.x += newFrame.size.width;
}
}

// Position divider.
theView = _dividerView;
newFrame.size.width = _splitWidth;
theView.frame = newFrame;
theView.frame = dividerRect;
if (!theView.superview) {
[self.view addSubview:theView];
}
newFrame.origin.x += newFrame.size.width;

// Position detail.
controller = self.detailViewController;
if (controller && [controller isKindOfClass:[UIViewController class]]) {
theView = controller.view;
if (theView) {
newFrame.size.width = width - newFrame.origin.x;
theView.frame = newFrame;
theView.frame = detailRect;
if (!theView.superview) {
[self.view insertSubview:theView aboveSubview:self.masterViewController.view];
} else {
Expand All @@ -316,44 +345,70 @@ - (void)layoutSubviewsForInterfaceOrientation:(UIInterfaceOrientation)theOrienta
}

} else {
// Master above, detail below.
if (![self shouldShowMasterForInterfaceOrientation:theOrientation]) {
// Move off-screen.
newFrame.origin.y -= (_splitPosition + _splitWidth);
// Master above, detail below (or vice versa).
CGRect masterRect, dividerRect, detailRect;
if (masterFirst) {
if (!shouldShowMaster) {
// Move off-screen.
newFrame.origin.y -= (_splitPosition + _splitWidth);
}

newFrame.size.height = _splitPosition;
masterRect = newFrame;

newFrame.origin.y += newFrame.size.height;
newFrame.size.height = _splitWidth;
dividerRect = newFrame;

newFrame.origin.y += newFrame.size.height;
newFrame.size.height = height - newFrame.origin.y;
detailRect = newFrame;

} else {
if (!shouldShowMaster) {
// Move off-screen.
newFrame.size.height += (_splitPosition + _splitWidth);
}

newFrame.size.height -= (_splitPosition + _splitWidth);
detailRect = newFrame;

newFrame.origin.y += newFrame.size.height;
newFrame.size.height = _splitWidth;
dividerRect = newFrame;

newFrame.origin.y += newFrame.size.height;
newFrame.size.height = _splitPosition;
masterRect = newFrame;
}

// Position master.
controller = self.masterViewController;
if (controller && [controller isKindOfClass:[UIViewController class]]) {
theView = controller.view;
if (theView) {
newFrame.size.height = _splitPosition;
theView.frame = newFrame;
theView.frame = masterRect;
if (!theView.superview) {
[self.masterViewController viewWillAppear:NO];
[controller viewWillAppear:NO];
[self.view addSubview:theView];
[self.masterViewController viewDidAppear:NO];
[controller viewDidAppear:NO];
}
newFrame.origin.y += newFrame.size.height;
}
}

// Position divider.
theView = _dividerView;
newFrame.size.height = _splitWidth;
theView.frame = newFrame;
theView.frame = dividerRect;
if (!theView.superview) {
[self.view addSubview:theView];
}
newFrame.origin.y += newFrame.size.height;

// Position detail.
controller = self.detailViewController;
if (controller && [controller isKindOfClass:[UIViewController class]]) {
theView = controller.view;
if (theView) {
newFrame.size.height = height - newFrame.origin.y;
theView.frame = newFrame;
theView.frame = detailRect;
if (!theView.superview) {
[self.view insertSubview:theView aboveSubview:self.masterViewController.view];
} else {
Expand Down Expand Up @@ -397,14 +452,14 @@ - (void)layoutSubviewsForInterfaceOrientation:(UIInterfaceOrientation)theOrienta
if (_vertical) { // left/right split
cornersWidth = (radius * 2.0) + _splitWidth;
cornersHeight = radius;
x = ((shouldShowMaster) ? _splitPosition : (0 - _splitWidth)) - radius;
x = ((shouldShowMaster) ? ((masterFirst) ? _splitPosition : width - (_splitPosition + _splitWidth)) : (0 - _splitWidth)) - radius;
y = 0;
leadingRect = CGRectMake(x, y, cornersWidth, cornersHeight); // top corners
trailingRect = CGRectMake(x, (height - cornersHeight), cornersWidth, cornersHeight); // bottom corners

} else { // top/bottom split
x = 0;
y = ((shouldShowMaster) ? _splitPosition : (0 - _splitWidth)) - radius;
y = ((shouldShowMaster) ? ((masterFirst) ? _splitPosition : height - (_splitPosition + _splitWidth)) : (0 - _splitWidth)) - radius;
cornersWidth = radius;
cornersHeight = (radius * 2.0) + _splitWidth;
leadingRect = CGRectMake(x, y, cornersWidth, cornersHeight); // left corners
Expand Down Expand Up @@ -521,7 +576,7 @@ - (void)reconfigureForMasterInPopover:(BOOL)inPopover

} else if (!inPopover && _hiddenPopoverController && _barButtonItem) {
// I know this looks strange, but it fixes a bizarre issue with UIPopoverController leaving masterViewController's views in disarray.
[_hiddenPopoverController presentPopoverFromBarButtonItem:_barButtonItem permittedArrowDirections:UIPopoverArrowDirectionAny animated:NO];
[_hiddenPopoverController presentPopoverFromRect:CGRectZero inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:NO];

// Remove master from popover and destroy popover, if it exists.
[_hiddenPopoverController dismissPopoverAnimated:NO];
Expand Down Expand Up @@ -566,7 +621,9 @@ - (void)notePopoverDismissed

- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
{
if ([animationID isEqualToString:MG_ANIMATION_CHANGE_SPLIT_ORIENTATION] && _cornerViews) {
if (([animationID isEqualToString:MG_ANIMATION_CHANGE_SPLIT_ORIENTATION] ||
[animationID isEqualToString:MG_ANIMATION_CHANGE_SUBVIEWS_ORDER])
&& _cornerViews) {
for (UIView *corner in _cornerViews) {
corner.hidden = NO;
}
Expand Down Expand Up @@ -600,6 +657,27 @@ - (IBAction)toggleSplitOrientation:(id)sender
}


- (IBAction)toggleMasterBeforeDetail:(id)sender
{
BOOL showingMaster = [self isShowingMaster];
if (showingMaster) {
if (_cornerViews) {
for (UIView *corner in _cornerViews) {
corner.hidden = YES;
}
_dividerView.hidden = YES;
}
[UIView beginAnimations:MG_ANIMATION_CHANGE_SUBVIEWS_ORDER context:nil];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];
}
self.masterBeforeDetail = (!self.masterBeforeDetail);
if (showingMaster) {
[UIView commitAnimations];
}
}


- (IBAction)toggleMasterView:(id)sender
{
if (_hiddenPopoverController && _hiddenPopoverController.popoverVisible) {
Expand Down Expand Up @@ -732,6 +810,28 @@ - (void)setVertical:(BOOL)flag
}


- (BOOL)isMasterBeforeDetail
{
return _masterBeforeDetail;
}


- (void)setMasterBeforeDetail:(BOOL)flag
{
if (flag != _masterBeforeDetail) {
if (_hiddenPopoverController && _hiddenPopoverController.popoverVisible) {
[_hiddenPopoverController dismissPopoverAnimated:NO];
}

_masterBeforeDetail = flag;

if ([self isShowingMaster]) {
[self layoutSubviews];
}
}
}


- (float)splitPosition
{
return _splitPosition;
Expand Down
Loading

0 comments on commit 5dcd21b

Please sign in to comment.