diff -r e1125559359f -r 2cccf6b2b89d project_files/HedgewarsMobile/MGSplitViewController.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/project_files/HedgewarsMobile/MGSplitViewController.m Thu Feb 09 18:59:46 2012 +0100 @@ -0,0 +1,1133 @@ +// +// MGSplitViewController.m +// MGSplitView +// +// Created by Matt Gemmell on 26/07/2010. +// Copyright 2010 Instinctive Code. +// + +#import "MGSplitViewController.h" +#import "MGSplitDividerView.h" +#import "MGSplitCornersView.h" + +#define MG_DEFAULT_SPLIT_POSITION 320.0 // default width of master view in UISplitViewController. +#define MG_DEFAULT_SPLIT_WIDTH 1.0 // default width of split-gutter in UISplitViewController. +#define MG_DEFAULT_CORNER_RADIUS 5.0 // default corner-radius of overlapping split-inner corners on the master and detail views. +#define MG_DEFAULT_CORNER_COLOR [UIColor blackColor] // default color of intruding inner corners (and divider background). + +#define MG_PANESPLITTER_CORNER_RADIUS 0.0 // corner-radius of split-inner corners for MGSplitViewDividerStylePaneSplitter style. +#define MG_PANESPLITTER_SPLIT_WIDTH 25.0 // width of split-gutter for MGSplitViewDividerStylePaneSplitter style. + +#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) + +- (void)setup; +- (CGSize)splitViewSizeForOrientation:(UIInterfaceOrientation)theOrientation; +- (void)layoutSubviews; +- (void)layoutSubviewsWithAnimation:(BOOL)animate; +- (void)layoutSubviewsForInterfaceOrientation:(UIInterfaceOrientation)theOrientation withAnimation:(BOOL)animate; +- (BOOL)shouldShowMasterForInterfaceOrientation:(UIInterfaceOrientation)theOrientation; +- (BOOL)shouldShowMaster; +- (NSString *)nameOfInterfaceOrientation:(UIInterfaceOrientation)theOrientation; +- (void)reconfigureForMasterInPopover:(BOOL)inPopover; + +@end + + +@implementation MGSplitViewController + + +#pragma mark - +#pragma mark Orientation helpers + + +- (NSString *)nameOfInterfaceOrientation:(UIInterfaceOrientation)theOrientation +{ + NSString *orientationName = nil; + switch (theOrientation) { + case UIInterfaceOrientationPortrait: + orientationName = @"Portrait"; // Home button at bottom + break; + case UIInterfaceOrientationPortraitUpsideDown: + orientationName = @"Portrait (Upside Down)"; // Home button at top + break; + case UIInterfaceOrientationLandscapeLeft: + orientationName = @"Landscape (Left)"; // Home button on left + break; + case UIInterfaceOrientationLandscapeRight: + orientationName = @"Landscape (Right)"; // Home button on right + break; + default: + break; + } + + return orientationName; +} + + +- (BOOL)isLandscape +{ + return UIInterfaceOrientationIsLandscape(self.interfaceOrientation); +} + + +- (BOOL)shouldShowMasterForInterfaceOrientation:(UIInterfaceOrientation)theOrientation +{ + // Returns YES if master view should be shown directly embedded in the splitview, instead of hidden in a popover. + return ((UIInterfaceOrientationIsLandscape(theOrientation)) ? _showsMasterInLandscape : _showsMasterInPortrait); +} + + +- (BOOL)shouldShowMaster +{ + return [self shouldShowMasterForInterfaceOrientation:self.interfaceOrientation]; +} + + +- (BOOL)isShowingMaster +{ + return [self shouldShowMaster] && self.masterViewController && self.masterViewController.view && ([self.masterViewController.view superview] == self.view); +} + + +#pragma mark - +#pragma mark Setup and Teardown + + +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil +{ + if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) { + [self setup]; + } + + return self; +} + + +- (id)initWithCoder:(NSCoder *)aDecoder +{ + if ((self = [super initWithCoder:aDecoder])) { + [self setup]; + } + + return self; +} + + +- (void)setup +{ + // Configure default behaviour. + _viewControllers = [[NSMutableArray alloc] initWithObjects:[NSNull null], [NSNull null], nil]; + _splitWidth = MG_DEFAULT_SPLIT_WIDTH; + _showsMasterInPortrait = NO; + _showsMasterInLandscape = YES; + _reconfigurePopup = NO; + _vertical = YES; + _masterBeforeDetail = YES; + _splitPosition = MG_DEFAULT_SPLIT_POSITION; + CGRect divRect = self.view.bounds; + if ([self isVertical]) { + divRect.origin.y = _splitPosition; + divRect.size.height = _splitWidth; + } else { + divRect.origin.x = _splitPosition; + divRect.size.width = _splitWidth; + } + _dividerView = [[MGSplitDividerView alloc] initWithFrame:divRect]; + _dividerView.splitViewController = self; + _dividerView.backgroundColor = MG_DEFAULT_CORNER_COLOR; + _dividerStyle = MGSplitViewDividerStyleThin; +} + + +- (void)dealloc +{ + _delegate = nil; + [self.view.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; + [_viewControllers release]; + [_barButtonItem release]; + [_hiddenPopoverController release]; + [_dividerView release]; + [_cornerViews release]; + + [super dealloc]; +} + + +#pragma mark - +#pragma mark View management + + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation +{ + return YES; +} + + +- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration +{ + [self.masterViewController willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration]; + [self.detailViewController willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration]; +} + + +- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation +{ + [self.masterViewController didRotateFromInterfaceOrientation:fromInterfaceOrientation]; + [self.detailViewController didRotateFromInterfaceOrientation:fromInterfaceOrientation]; +} + + +- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation + duration:(NSTimeInterval)duration +{ + [self.masterViewController willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration]; + [self.detailViewController willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration]; + + // Hide popover. + if (_hiddenPopoverController && _hiddenPopoverController.popoverVisible) { + [_hiddenPopoverController dismissPopoverAnimated:NO]; + } + + // Re-tile views. + _reconfigurePopup = YES; + [self layoutSubviewsForInterfaceOrientation:toInterfaceOrientation withAnimation:YES]; +} + + +- (void)willAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration +{ + [self.masterViewController willAnimateFirstHalfOfRotationToInterfaceOrientation:toInterfaceOrientation duration:duration]; + [self.detailViewController willAnimateFirstHalfOfRotationToInterfaceOrientation:toInterfaceOrientation duration:duration]; +} + + +- (void)didAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation +{ + [self.masterViewController didAnimateFirstHalfOfRotationToInterfaceOrientation:toInterfaceOrientation]; + [self.detailViewController didAnimateFirstHalfOfRotationToInterfaceOrientation:toInterfaceOrientation]; +} + + +- (void)willAnimateSecondHalfOfRotationFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation duration:(NSTimeInterval)duration +{ + [self.masterViewController willAnimateSecondHalfOfRotationFromInterfaceOrientation:fromInterfaceOrientation duration:duration]; + [self.detailViewController willAnimateSecondHalfOfRotationFromInterfaceOrientation:fromInterfaceOrientation duration:duration]; +} + + +- (CGSize)splitViewSizeForOrientation:(UIInterfaceOrientation)theOrientation +{ + UIScreen *screen = [UIScreen mainScreen]; + CGRect fullScreenRect = screen.bounds; // always implicitly in Portrait orientation. + CGRect appFrame = screen.applicationFrame; + + // Find status bar height by checking which dimension of the applicationFrame is narrower than screen bounds. + // Little bit ugly looking, but it'll still work even if they change the status bar height in future. + float statusBarHeight = MAX((fullScreenRect.size.width - appFrame.size.width), (fullScreenRect.size.height - appFrame.size.height)); + + // Initially assume portrait orientation. + float width = fullScreenRect.size.width; + float height = fullScreenRect.size.height; + + // Correct for orientation. + if (UIInterfaceOrientationIsLandscape(theOrientation)) { + width = height; + height = fullScreenRect.size.width; + } + + // Account for status bar, which always subtracts from the height (since it's always at the top of the screen). + height -= statusBarHeight; + + return CGSizeMake(width, height); +} + + +- (void)layoutSubviewsForInterfaceOrientation:(UIInterfaceOrientation)theOrientation withAnimation:(BOOL)animate +{ + if (_reconfigurePopup) { + [self reconfigureForMasterInPopover:![self shouldShowMasterForInterfaceOrientation:theOrientation]]; + } + + // Layout the master, detail and divider views appropriately, adding/removing subviews as needed. + // First obtain relevant geometry. + CGSize fullSize = [self splitViewSizeForOrientation:theOrientation]; + float width = fullSize.width; + float height = fullSize.height; + + if (NO) { // Just for debugging. + NSLog(@"Target orientation is %@, dimensions will be %.0f x %.0f", + [self nameOfInterfaceOrientation:theOrientation], width, height); + } + + // Layout the master, divider and detail views. + CGRect newFrame = CGRectMake(0, 0, width, height); + UIViewController *controller; + UIView *theView; + BOOL shouldShowMaster = [self shouldShowMasterForInterfaceOrientation:theOrientation]; + BOOL masterFirst = [self isMasterBeforeDetail]; + if ([self isVertical]) { + // 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) { + theView.frame = masterRect; + if (!theView.superview) { + [controller viewWillAppear:NO]; + [self.view addSubview:theView]; + [controller viewDidAppear:NO]; + } + } + } + + // Position divider. + theView = _dividerView; + theView.frame = dividerRect; + if (!theView.superview) { + [self.view addSubview:theView]; + } + + // Position detail. + controller = self.detailViewController; + if (controller && [controller isKindOfClass:[UIViewController class]]) { + theView = controller.view; + if (theView) { + theView.frame = detailRect; + if (!theView.superview) { + [self.view insertSubview:theView aboveSubview:self.masterViewController.view]; + } else { + [self.view bringSubviewToFront:theView]; + } + } + } + + } else { + // 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) { + theView.frame = masterRect; + if (!theView.superview) { + [controller viewWillAppear:NO]; + [self.view addSubview:theView]; + [controller viewDidAppear:NO]; + } + } + } + + // Position divider. + theView = _dividerView; + theView.frame = dividerRect; + if (!theView.superview) { + [self.view addSubview:theView]; + } + + // Position detail. + controller = self.detailViewController; + if (controller && [controller isKindOfClass:[UIViewController class]]) { + theView = controller.view; + if (theView) { + theView.frame = detailRect; + if (!theView.superview) { + [self.view insertSubview:theView aboveSubview:self.masterViewController.view]; + } else { + [self.view bringSubviewToFront:theView]; + } + } + } + } + + // Create corner views if necessary. + MGSplitCornersView *leadingCorners; // top/left of screen in vertical/horizontal split. + MGSplitCornersView *trailingCorners; // bottom/right of screen in vertical/horizontal split. + if (!_cornerViews) { + CGRect cornerRect = CGRectMake(0, 0, 10, 10); // arbitrary, will be resized below. + leadingCorners = [[MGSplitCornersView alloc] initWithFrame:cornerRect]; + leadingCorners.splitViewController = self; + leadingCorners.cornerBackgroundColor = MG_DEFAULT_CORNER_COLOR; + leadingCorners.cornerRadius = MG_DEFAULT_CORNER_RADIUS; + trailingCorners = [[MGSplitCornersView alloc] initWithFrame:cornerRect]; + trailingCorners.splitViewController = self; + trailingCorners.cornerBackgroundColor = MG_DEFAULT_CORNER_COLOR; + trailingCorners.cornerRadius = MG_DEFAULT_CORNER_RADIUS; + _cornerViews = [[NSArray alloc] initWithObjects:leadingCorners, trailingCorners, nil]; + [leadingCorners release]; + [trailingCorners release]; + + } else if ([_cornerViews count] == 2) { + leadingCorners = [_cornerViews objectAtIndex:0]; + trailingCorners = [_cornerViews objectAtIndex:1]; + } + + // Configure and layout the corner-views. + leadingCorners.cornersPosition = (_vertical) ? MGCornersPositionLeadingVertical : MGCornersPositionLeadingHorizontal; + trailingCorners.cornersPosition = (_vertical) ? MGCornersPositionTrailingVertical : MGCornersPositionTrailingHorizontal; + leadingCorners.autoresizingMask = (_vertical) ? UIViewAutoresizingFlexibleBottomMargin : UIViewAutoresizingFlexibleRightMargin; + trailingCorners.autoresizingMask = (_vertical) ? UIViewAutoresizingFlexibleTopMargin : UIViewAutoresizingFlexibleLeftMargin; + + float x, y, cornersWidth, cornersHeight; + CGRect leadingRect, trailingRect; + float radius = leadingCorners.cornerRadius; + if (_vertical) { // left/right split + cornersWidth = (radius * 2.0) + _splitWidth; + cornersHeight = 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) ? ((masterFirst) ? _splitPosition : height - (_splitPosition + _splitWidth)) : (0 - _splitWidth)) - radius; + cornersWidth = radius; + cornersHeight = (radius * 2.0) + _splitWidth; + leadingRect = CGRectMake(x, y, cornersWidth, cornersHeight); // left corners + trailingRect = CGRectMake((width - cornersWidth), y, cornersWidth, cornersHeight); // right corners + } + + leadingCorners.frame = leadingRect; + trailingCorners.frame = trailingRect; + + // Ensure corners are visible and frontmost. + if (!leadingCorners.superview) { + [self.view insertSubview:leadingCorners aboveSubview:self.detailViewController.view]; + [self.view insertSubview:trailingCorners aboveSubview:self.detailViewController.view]; + } else { + [self.view bringSubviewToFront:leadingCorners]; + [self.view bringSubviewToFront:trailingCorners]; + } +} + + +- (void)layoutSubviewsWithAnimation:(BOOL)animate +{ + [self layoutSubviewsForInterfaceOrientation:self.interfaceOrientation withAnimation:animate]; +} + + +- (void)layoutSubviews +{ + [self layoutSubviewsForInterfaceOrientation:self.interfaceOrientation withAnimation:YES]; +} + + +- (void)viewWillAppear:(BOOL)animated +{ + [super viewWillAppear:animated]; + + if ([self isShowingMaster]) { + [self.masterViewController viewWillAppear:animated]; + } + [self.detailViewController viewWillAppear:animated]; + + _reconfigurePopup = YES; + [self layoutSubviews]; +} + + +- (void)viewDidAppear:(BOOL)animated +{ + [super viewDidAppear:animated]; + + if ([self isShowingMaster]) { + [self.masterViewController viewDidAppear:animated]; + } + [self.detailViewController viewDidAppear:animated]; +} + + +- (void)viewWillDisappear:(BOOL)animated +{ + [super viewWillDisappear:animated]; + + if ([self isShowingMaster]) { + [self.masterViewController viewWillDisappear:animated]; + } + [self.detailViewController viewWillDisappear:animated]; +} + + +- (void)viewDidDisappear:(BOOL)animated +{ + [super viewDidDisappear:animated]; + + if ([self isShowingMaster]) { + [self.masterViewController viewDidDisappear:animated]; + } + [self.detailViewController viewDidDisappear:animated]; +} + + +#pragma mark - +#pragma mark Popover handling + + +- (void)reconfigureForMasterInPopover:(BOOL)inPopover +{ + _reconfigurePopup = NO; + + if ((inPopover && _hiddenPopoverController) || (!inPopover && !_hiddenPopoverController) || !self.masterViewController) { + // Nothing to do. + return; + } + + if (inPopover && !_hiddenPopoverController && !_barButtonItem) { + // Create and configure popover for our masterViewController. + [_hiddenPopoverController release]; + _hiddenPopoverController = nil; + [self.masterViewController viewWillDisappear:NO]; + _hiddenPopoverController = [[UIPopoverController alloc] initWithContentViewController:self.masterViewController]; + [self.masterViewController viewDidDisappear:NO]; + + // Create and configure _barButtonItem. + _barButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Master", nil) + style:UIBarButtonItemStyleBordered + target:self + action:@selector(showMasterPopover:)]; + + // Inform delegate of this state of affairs. + if (_delegate && [_delegate respondsToSelector:@selector(splitViewController:willHideViewController:withBarButtonItem:forPopoverController:)]) { + [(NSObject *)_delegate splitViewController:self + willHideViewController:self.masterViewController + withBarButtonItem:_barButtonItem + forPopoverController:_hiddenPopoverController]; + } + + } 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 presentPopoverFromRect:CGRectZero inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:NO]; + + // Remove master from popover and destroy popover, if it exists. + [_hiddenPopoverController dismissPopoverAnimated:NO]; + [_hiddenPopoverController release]; + _hiddenPopoverController = nil; + + // Inform delegate that the _barButtonItem will become invalid. + if (_delegate && [_delegate respondsToSelector:@selector(splitViewController:willShowViewController:invalidatingBarButtonItem:)]) { + [(NSObject *)_delegate splitViewController:self + willShowViewController:self.masterViewController + invalidatingBarButtonItem:_barButtonItem]; + } + + // Destroy _barButtonItem. + [_barButtonItem release]; + _barButtonItem = nil; + + // Move master view. + UIView *masterView = self.masterViewController.view; + if (masterView && masterView.superview != self.view) { + [masterView removeFromSuperview]; + } + } +} + + +- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController +{ + [self reconfigureForMasterInPopover:NO]; +} + + +- (void)notePopoverDismissed +{ + [self popoverControllerDidDismissPopover:_hiddenPopoverController]; +} + + +#pragma mark - +#pragma mark Animations + + +- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context +{ + if (([animationID isEqualToString:MG_ANIMATION_CHANGE_SPLIT_ORIENTATION] || + [animationID isEqualToString:MG_ANIMATION_CHANGE_SUBVIEWS_ORDER]) + && _cornerViews) { + for (UIView *corner in _cornerViews) { + corner.hidden = NO; + } + _dividerView.hidden = NO; + } +} + + +#pragma mark - +#pragma mark IB Actions + + +- (IBAction)toggleSplitOrientation:(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_SPLIT_ORIENTATION context:nil]; + [UIView setAnimationDelegate:self]; + [UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)]; + } + self.vertical = (!self.vertical); + if (showingMaster) { + [UIView commitAnimations]; + } +} + + +- (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) { + [_hiddenPopoverController dismissPopoverAnimated:NO]; + } + + if (![self isShowingMaster]) { + // We're about to show the master view. Ensure it's in place off-screen to be animated in. + _reconfigurePopup = YES; + [self reconfigureForMasterInPopover:NO]; + [self layoutSubviews]; + } + + // This action functions on the current primary orientation; it is independent of the other primary orientation. + [UIView beginAnimations:@"toggleMaster" context:nil]; + if (self.isLandscape) { + self.showsMasterInLandscape = !_showsMasterInLandscape; + } else { + self.showsMasterInPortrait = !_showsMasterInPortrait; + } + [UIView commitAnimations]; +} + + +- (IBAction)showMasterPopover:(id)sender +{ + if (_hiddenPopoverController && !(_hiddenPopoverController.popoverVisible)) { + // Inform delegate. + if (_delegate && [_delegate respondsToSelector:@selector(splitViewController:popoverController:willPresentViewController:)]) { + [(NSObject *)_delegate splitViewController:self + popoverController:_hiddenPopoverController + willPresentViewController:self.masterViewController]; + } + + // Show popover. + [_hiddenPopoverController presentPopoverFromBarButtonItem:_barButtonItem permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; + } +} + + +#pragma mark - +#pragma mark Accessors and properties + + +- (id)delegate +{ + return _delegate; +} + + +- (void)setDelegate:(id )newDelegate +{ + if (newDelegate != _delegate && + (!newDelegate || [(NSObject *)newDelegate conformsToProtocol:@protocol(MGSplitViewControllerDelegate)])) { + _delegate = newDelegate; + } +} + + +- (BOOL)showsMasterInPortrait +{ + return _showsMasterInPortrait; +} + + +- (void)setShowsMasterInPortrait:(BOOL)flag +{ + if (flag != _showsMasterInPortrait) { + _showsMasterInPortrait = flag; + + if (![self isLandscape]) { // i.e. if this will cause a visual change. + if (_hiddenPopoverController && _hiddenPopoverController.popoverVisible) { + [_hiddenPopoverController dismissPopoverAnimated:NO]; + } + + // Rearrange views. + _reconfigurePopup = YES; + [self layoutSubviews]; + } + } +} + + +- (BOOL)showsMasterInLandscape +{ + return _showsMasterInLandscape; +} + + +- (void)setShowsMasterInLandscape:(BOOL)flag +{ + if (flag != _showsMasterInLandscape) { + _showsMasterInLandscape = flag; + + if ([self isLandscape]) { // i.e. if this will cause a visual change. + if (_hiddenPopoverController && _hiddenPopoverController.popoverVisible) { + [_hiddenPopoverController dismissPopoverAnimated:NO]; + } + + // Rearrange views. + _reconfigurePopup = YES; + [self layoutSubviews]; + } + } +} + + +- (BOOL)isVertical +{ + return _vertical; +} + + +- (void)setVertical:(BOOL)flag +{ + if (flag != _vertical) { + if (_hiddenPopoverController && _hiddenPopoverController.popoverVisible) { + [_hiddenPopoverController dismissPopoverAnimated:NO]; + } + + _vertical = flag; + + // Inform delegate. + if (_delegate && [_delegate respondsToSelector:@selector(splitViewController:willChangeSplitOrientationToVertical:)]) { + [_delegate splitViewController:self willChangeSplitOrientationToVertical:_vertical]; + } + + [self layoutSubviews]; + } +} + + +- (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; +} + + +- (void)setSplitPosition:(float)posn +{ + // Check to see if delegate wishes to constrain the position. + float newPosn = posn; + BOOL constrained = NO; + CGSize fullSize = [self splitViewSizeForOrientation:self.interfaceOrientation]; + if (_delegate && [_delegate respondsToSelector:@selector(splitViewController:constrainSplitPosition:splitViewSize:)]) { + newPosn = [_delegate splitViewController:self constrainSplitPosition:newPosn splitViewSize:fullSize]; + constrained = YES; // implicitly trust delegate's response. + + } else { + // Apply default constraints if delegate doesn't wish to participate. + float minPos = MG_MIN_VIEW_WIDTH; + float maxPos = ((_vertical) ? fullSize.width : fullSize.height) - (MG_MIN_VIEW_WIDTH + _splitWidth); + constrained = (newPosn != _splitPosition && newPosn >= minPos && newPosn <= maxPos); + } + + if (constrained) { + if (_hiddenPopoverController && _hiddenPopoverController.popoverVisible) { + [_hiddenPopoverController dismissPopoverAnimated:NO]; + } + + _splitPosition = newPosn; + + // Inform delegate. + if (_delegate && [_delegate respondsToSelector:@selector(splitViewController:willMoveSplitToPosition:)]) { + [_delegate splitViewController:self willMoveSplitToPosition:_splitPosition]; + } + + if ([self isShowingMaster]) { + [self layoutSubviews]; + } + } +} + + +- (void)setSplitPosition:(float)posn animated:(BOOL)animate +{ + BOOL shouldAnimate = (animate && [self isShowingMaster]); + if (shouldAnimate) { + [UIView beginAnimations:@"SplitPosition" context:nil]; + } + [self setSplitPosition:posn]; + if (shouldAnimate) { + [UIView commitAnimations]; + } +} + + +- (float)splitWidth +{ + return _splitWidth; +} + + +- (void)setSplitWidth:(float)width +{ + if (width != _splitWidth && width >= 0) { + _splitWidth = width; + if ([self isShowingMaster]) { + [self layoutSubviews]; + } + } +} + + +- (NSArray *)viewControllers +{ + return [[_viewControllers copy] autorelease]; +} + + +- (void)setViewControllers:(NSArray *)controllers +{ + if (controllers != _viewControllers) { + for (UIViewController *controller in _viewControllers) { + if ([controller isKindOfClass:[UIViewController class]]) { + [controller.view removeFromSuperview]; + } + } + [_viewControllers release]; + _viewControllers = [[NSMutableArray alloc] initWithCapacity:2]; + if (controllers && [controllers count] >= 2) { + self.masterViewController = [controllers objectAtIndex:0]; + self.detailViewController = [controllers objectAtIndex:1]; + } else { + NSLog(@"Error: %@ requires 2 view-controllers. (%@)", NSStringFromClass([self class]), NSStringFromSelector(_cmd)); + } + + [self layoutSubviews]; + } +} + + +- (UIViewController *)masterViewController +{ + if (_viewControllers && [_viewControllers count] > 0) { + NSObject *controller = [_viewControllers objectAtIndex:0]; + if ([controller isKindOfClass:[UIViewController class]]) { + return [[controller retain] autorelease]; + } + } + + return nil; +} + + +- (void)setMasterViewController:(UIViewController *)master +{ + if (!_viewControllers) { + _viewControllers = [[NSMutableArray alloc] initWithCapacity:2]; + } + + NSObject *newMaster = master; + if (!newMaster) { + newMaster = [NSNull null]; + } + + BOOL changed = YES; + if ([_viewControllers count] > 0) { + if ([_viewControllers objectAtIndex:0] == newMaster) { + changed = NO; + } else { + [_viewControllers replaceObjectAtIndex:0 withObject:newMaster]; + } + + } else { + [_viewControllers addObject:newMaster]; + } + + if (changed) { + [self layoutSubviews]; + } +} + + +- (UIViewController *)detailViewController +{ + if (_viewControllers && [_viewControllers count] > 1) { + NSObject *controller = [_viewControllers objectAtIndex:1]; + if ([controller isKindOfClass:[UIViewController class]]) { + return [[controller retain] autorelease]; + } + } + + return nil; +} + + +- (void)setDetailViewController:(UIViewController *)detail +{ + if (!_viewControllers) { + _viewControllers = [[NSMutableArray alloc] initWithCapacity:2]; + [_viewControllers addObject:[NSNull null]]; + } + + BOOL changed = YES; + if ([_viewControllers count] > 1) { + if ([_viewControllers objectAtIndex:1] == detail) { + changed = NO; + } else { + [_viewControllers replaceObjectAtIndex:1 withObject:detail]; + } + + } else { + [_viewControllers addObject:detail]; + } + + if (changed) { + [self layoutSubviews]; + } +} + + +- (MGSplitDividerView *)dividerView +{ + return [[_dividerView retain] autorelease]; +} + + +- (void)setDividerView:(MGSplitDividerView *)divider +{ + if (divider != _dividerView) { + [_dividerView removeFromSuperview]; + [_dividerView release]; + _dividerView = [divider retain]; + _dividerView.splitViewController = self; + _dividerView.backgroundColor = MG_DEFAULT_CORNER_COLOR; + if ([self isShowingMaster]) { + [self layoutSubviews]; + } + } +} + + +- (BOOL)allowsDraggingDivider +{ + if (_dividerView) { + return _dividerView.allowsDragging; + } + + return NO; +} + + +- (void)setAllowsDraggingDivider:(BOOL)flag +{ + if (self.allowsDraggingDivider != flag && _dividerView) { + _dividerView.allowsDragging = flag; + } +} + + +- (MGSplitViewDividerStyle)dividerStyle +{ + return _dividerStyle; +} + + +- (void)setDividerStyle:(MGSplitViewDividerStyle)newStyle +{ + if (_hiddenPopoverController && _hiddenPopoverController.popoverVisible) { + [_hiddenPopoverController dismissPopoverAnimated:NO]; + } + + // We don't check to see if newStyle equals _dividerStyle, because it's a meta-setting. + // Aspects could have been changed since it was set. + _dividerStyle = newStyle; + + // Reconfigure general appearance and behaviour. + float cornerRadius; + if (_dividerStyle == MGSplitViewDividerStyleThin) { + cornerRadius = MG_DEFAULT_CORNER_RADIUS; + _splitWidth = MG_DEFAULT_SPLIT_WIDTH; + self.allowsDraggingDivider = NO; + + } else if (_dividerStyle == MGSplitViewDividerStylePaneSplitter) { + cornerRadius = MG_PANESPLITTER_CORNER_RADIUS; + _splitWidth = MG_PANESPLITTER_SPLIT_WIDTH; + self.allowsDraggingDivider = YES; + } + + // Update divider and corners. + [_dividerView setNeedsDisplay]; + if (_cornerViews) { + for (MGSplitCornersView *corner in _cornerViews) { + corner.cornerRadius = cornerRadius; + } + } + + // Layout all views. + [self layoutSubviews]; +} + + +- (void)setDividerStyle:(MGSplitViewDividerStyle)newStyle animated:(BOOL)animate +{ + BOOL shouldAnimate = (animate && [self isShowingMaster]); + if (shouldAnimate) { + [UIView beginAnimations:@"DividerStyle" context:nil]; + } + [self setDividerStyle:newStyle]; + if (shouldAnimate) { + [UIView commitAnimations]; + } +} + + +- (NSArray *)cornerViews +{ + if (_cornerViews) { + return [[_cornerViews retain] autorelease]; + } + + return nil; +} + + +@synthesize showsMasterInPortrait; +@synthesize showsMasterInLandscape; +@synthesize vertical; +@synthesize delegate; +@synthesize viewControllers; +@synthesize masterViewController; +@synthesize detailViewController; +@synthesize dividerView; +@synthesize splitPosition; +@synthesize splitWidth; +@synthesize allowsDraggingDivider; +@synthesize dividerStyle; + + +@end