# HG changeset patch # User koda # Date 1328810386 -3600 # Node ID 2cccf6b2b89d281597f5d843a73aa1cf8a9f115f # Parent e1125559359f47f576fd683859f94697b39189ea added MGSplitViewController, popular replacement for uisplitviewcontrollers: this brings rotation support to our settings pages! weapons and schemes are the only controllers displaying minor glitches diff -r e1125559359f -r 2cccf6b2b89d project_files/HedgewarsMobile/Classes/SettingsContainerViewController.h --- a/project_files/HedgewarsMobile/Classes/SettingsContainerViewController.h Thu Feb 09 18:09:03 2012 +0100 +++ b/project_files/HedgewarsMobile/Classes/SettingsContainerViewController.h Thu Feb 09 18:59:46 2012 +0100 @@ -22,15 +22,14 @@ #import @class SettingsBaseViewController; +@class MGSplitViewController; @interface SettingsContainerViewController : UIViewController { SettingsBaseViewController *baseController; - UINavigationController *activeController; - UISplitViewController *splitViewRootController; + MGSplitViewController *splitViewRootController; } @property (nonatomic,retain) SettingsBaseViewController *baseController; -@property (nonatomic,retain) UINavigationController *activeController; -@property (nonatomic,retain) UISplitViewController *splitViewRootController; +@property (nonatomic,retain) MGSplitViewController *splitViewRootController; @end diff -r e1125559359f -r 2cccf6b2b89d project_files/HedgewarsMobile/Classes/SettingsContainerViewController.m --- a/project_files/HedgewarsMobile/Classes/SettingsContainerViewController.m Thu Feb 09 18:09:03 2012 +0100 +++ b/project_files/HedgewarsMobile/Classes/SettingsContainerViewController.m Thu Feb 09 18:59:46 2012 +0100 @@ -21,10 +21,11 @@ #import "SettingsContainerViewController.h" #import "SettingsBaseViewController.h" +#import "MGSplitViewController.h" @implementation SettingsContainerViewController -@synthesize baseController, activeController, splitViewRootController; +@synthesize baseController, splitViewRootController; -(BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { return rotationManager(interfaceOrientation); @@ -32,8 +33,8 @@ -(void) viewDidLoad { - CGRect rect = [[UIScreen mainScreen] bounds]; - self.view.frame = CGRectMake(0, 0, rect.size.height, rect.size.width); + CGRect screenRect = [[UIScreen mainScreen] safeBounds]; + self.view.frame = screenRect; if (IS_IPAD()) { // the contents on the right of the splitview, setting targetController to nil to avoid creating the table @@ -48,11 +49,11 @@ UINavigationController *leftNavController = [[UINavigationController alloc] initWithRootViewController:leftController]; [leftController release]; - self.activeController = rightNavController; - self.splitViewRootController = [[UISplitViewController alloc] init]; + self.splitViewRootController = [[MGSplitViewController alloc] init]; self.splitViewRootController.delegate = nil; - self.splitViewRootController.view.frame = CGRectMake(0, 0, rect.size.height, rect.size.width); + self.splitViewRootController.view.frame = screenRect; self.splitViewRootController.viewControllers = [NSArray arrayWithObjects: leftNavController, rightNavController, nil]; + self.splitViewRootController.showsMasterInPortrait = YES; [leftNavController release]; [rightNavController release]; @@ -65,7 +66,7 @@ [sbvc release]; } self.baseController.targetController = nil; - self.baseController.view.frame = CGRectMake(0, 0, rect.size.height, rect.size.width); + self.baseController.view.frame = CGRectMake(0, 0, screenRect.size.height, screenRect.size.width); [self.view addSubview:self.baseController.view]; } @@ -78,8 +79,6 @@ -(void) didReceiveMemoryWarning { if (self.baseController.view.superview == nil) self.baseController = nil; - if (self.activeController.view.superview == nil) - self.activeController = nil; if (self.splitViewRootController.view.superview == nil) self.splitViewRootController = nil; MSG_MEMCLEAN(); @@ -88,7 +87,6 @@ -(void) viewDidUnload { self.baseController = nil; - self.activeController = nil; self.splitViewRootController = nil; MSG_DIDUNLOAD(); [super viewDidUnload]; @@ -96,7 +94,6 @@ -(void) dealloc { releaseAndNil(baseController); - releaseAndNil(activeController); releaseAndNil(splitViewRootController); [super dealloc]; } @@ -107,28 +104,35 @@ // every time we add a uiviewcontroller programmatically we need to take care of propgating such messages // see http://davidebenini.it/2009/01/03/viewwillappear-not-being-called-inside-a-uinavigationcontroller/ -(void) viewWillAppear:(BOOL)animated { - [self.activeController viewWillAppear:animated]; + [self.splitViewRootController.detailViewController viewWillAppear:animated]; [self.baseController viewWillAppear:animated]; [super viewWillAppear:animated]; } -(void) viewWillDisappear:(BOOL)animated { - [self.activeController viewWillDisappear:animated]; + [self.splitViewRootController.detailViewController viewWillDisappear:animated]; [self.baseController viewWillDisappear:animated]; [super viewWillDisappear:animated]; } -(void) viewDidAppear:(BOOL)animated { - [self.activeController viewDidAppear:animated]; + [self.splitViewRootController.detailViewController viewDidAppear:animated]; [self.baseController viewDidAppear:animated]; [super viewDidLoad]; } -(void) viewDidDisappear:(BOOL)animated { - [self.activeController viewDidDisappear:animated]; + [self.splitViewRootController.detailViewController viewDidDisappear:animated]; [self.baseController viewDidDisappear:animated]; [super viewDidUnload]; } +-(void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { + if (IS_IPAD() == NO) + return; + + CGRect screenRect = [[UIScreen mainScreen] safeBounds]; + self.splitViewRootController.masterViewController.view.frame = CGRectMake(0, 0, 320, screenRect.size.height); +} @end diff -r e1125559359f -r 2cccf6b2b89d project_files/HedgewarsMobile/Classes/SupportViewController.m --- a/project_files/HedgewarsMobile/Classes/SupportViewController.m Thu Feb 09 18:09:03 2012 +0100 +++ b/project_files/HedgewarsMobile/Classes/SupportViewController.m Thu Feb 09 18:59:46 2012 +0100 @@ -143,14 +143,18 @@ -(UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger) section { if (section == 1) { UIView *footer = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.tableView.frame.size.width, 240)]; + footer.autoresizingMask = UIViewAutoresizingFlexibleWidth; + UIImage *img = [[UIImage alloc] initWithContentsOfFile:@"surprise.png"]; UIImageView *imgView = [[UIImageView alloc] initWithImage:img]; [img release]; imgView.center = CGPointMake(self.tableView.frame.size.width/2, 120); + imgView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin; [footer addSubview:imgView]; [imgView release]; UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.tableView.frame.size.width, 20)]; + label.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin; label.textAlignment = UITextAlignmentCenter; label.text = @" ♥ THANK YOU ♥ "; label.backgroundColor = [UIColor clearColor]; diff -r e1125559359f -r 2cccf6b2b89d project_files/HedgewarsMobile/Hedgewars.xcodeproj/project.pbxproj --- a/project_files/HedgewarsMobile/Hedgewars.xcodeproj/project.pbxproj Thu Feb 09 18:09:03 2012 +0100 +++ b/project_files/HedgewarsMobile/Hedgewars.xcodeproj/project.pbxproj Thu Feb 09 18:59:46 2012 +0100 @@ -90,6 +90,9 @@ 615AD9E9120764CA00F2FF04 /* backButton.png in Resources */ = {isa = PBXBuildFile; fileRef = 615AD9E8120764CA00F2FF04 /* backButton.png */; }; 615AD9EB1207654E00F2FF04 /* helpButton.png in Resources */ = {isa = PBXBuildFile; fileRef = 615AD9EA1207654E00F2FF04 /* helpButton.png */; }; 615E755A14E41E8C00FBA131 /* MXAudioPlayerFadeOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 615E755914E41E8C00FBA131 /* MXAudioPlayerFadeOperation.m */; }; + 615E75C714E42CAA00FBA131 /* MGSplitCornersView.m in Sources */ = {isa = PBXBuildFile; fileRef = 615E75C214E42CAA00FBA131 /* MGSplitCornersView.m */; }; + 615E75C814E42CAA00FBA131 /* MGSplitDividerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 615E75C414E42CAA00FBA131 /* MGSplitDividerView.m */; }; + 615E75C914E42CAA00FBA131 /* MGSplitViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 615E75C614E42CAA00FBA131 /* MGSplitViewController.m */; }; 615FEAE212A2A6640098EE92 /* localplayButton~ipad.png in Resources */ = {isa = PBXBuildFile; fileRef = 615FEADF12A2A6640098EE92 /* localplayButton~ipad.png */; }; 615FEAE312A2A6640098EE92 /* localplayButton~iphone.png in Resources */ = {isa = PBXBuildFile; fileRef = 615FEAE012A2A6640098EE92 /* localplayButton~iphone.png */; }; 6163EE7E11CC2600001C0453 /* SingleWeaponViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6163EE7D11CC2600001C0453 /* SingleWeaponViewController.m */; }; @@ -440,6 +443,13 @@ 615AD9EA1207654E00F2FF04 /* helpButton.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = helpButton.png; path = Resources/Frontend/helpButton.png; sourceTree = ""; }; 615E755814E41E8C00FBA131 /* MXAudioPlayerFadeOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXAudioPlayerFadeOperation.h; sourceTree = ""; }; 615E755914E41E8C00FBA131 /* MXAudioPlayerFadeOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXAudioPlayerFadeOperation.m; sourceTree = ""; }; + 615E75C114E42CAA00FBA131 /* MGSplitCornersView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGSplitCornersView.h; sourceTree = ""; }; + 615E75C214E42CAA00FBA131 /* MGSplitCornersView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGSplitCornersView.m; sourceTree = ""; }; + 615E75C314E42CAA00FBA131 /* MGSplitDividerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGSplitDividerView.h; sourceTree = ""; }; + 615E75C414E42CAA00FBA131 /* MGSplitDividerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGSplitDividerView.m; sourceTree = ""; }; + 615E75C514E42CAA00FBA131 /* MGSplitViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGSplitViewController.h; sourceTree = ""; }; + 615E75C614E42CAA00FBA131 /* MGSplitViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGSplitViewController.m; sourceTree = ""; }; + 615E76B514E4406400FBA131 /* LICENCE.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = LICENCE.rtf; sourceTree = ""; }; 615FEAD912A2A4C10098EE92 /* checkbox@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "checkbox@2x.png"; path = "Resources/Icons/checkbox@2x.png"; sourceTree = ""; }; 615FEADE12A2A6640098EE92 /* localplayButton@2x~iphone.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "localplayButton@2x~iphone.png"; path = "Resources/Frontend/localplayButton@2x~iphone.png"; sourceTree = ""; }; 615FEADF12A2A6640098EE92 /* localplayButton~ipad.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "localplayButton~ipad.png"; path = "Resources/Frontend/localplayButton~ipad.png"; sourceTree = ""; }; @@ -885,6 +895,20 @@ name = Sounds; sourceTree = ""; }; + 615E75C014E42C9000FBA131 /* MGSplitViewController */ = { + isa = PBXGroup; + children = ( + 615E76B514E4406400FBA131 /* LICENCE.rtf */, + 615E75C114E42CAA00FBA131 /* MGSplitCornersView.h */, + 615E75C214E42CAA00FBA131 /* MGSplitCornersView.m */, + 615E75C314E42CAA00FBA131 /* MGSplitDividerView.h */, + 615E75C414E42CAA00FBA131 /* MGSplitDividerView.m */, + 615E75C514E42CAA00FBA131 /* MGSplitViewController.h */, + 615E75C614E42CAA00FBA131 /* MGSplitViewController.m */, + ); + name = MGSplitViewController; + sourceTree = ""; + }; 6163EE4C11CC2478001C0453 /* Settings Page */ = { isa = PBXGroup; children = ( @@ -1101,6 +1125,7 @@ 61DE91561258B76800B80214 /* Custom UIs */ = { isa = PBXGroup; children = ( + 615E75C014E42C9000FBA131 /* MGSplitViewController */, 610C8E3514E018D200CF5C4C /* ValueTrackingSliderView.h */, 610C8E3614E018D200CF5C4C /* ValueTrackingSliderView.m */, 619C5BA0124FA59000D041AE /* MapPreviewButtonView.h */, @@ -1740,6 +1765,9 @@ 61D08D7614AEA7FE0007C078 /* uGearsUtils.pas in Sources */, 610C8E3714E018D200CF5C4C /* ValueTrackingSliderView.m in Sources */, 615E755A14E41E8C00FBA131 /* MXAudioPlayerFadeOperation.m in Sources */, + 615E75C714E42CAA00FBA131 /* MGSplitCornersView.m in Sources */, + 615E75C814E42CAA00FBA131 /* MGSplitDividerView.m in Sources */, + 615E75C914E42CAA00FBA131 /* MGSplitViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff -r e1125559359f -r 2cccf6b2b89d project_files/HedgewarsMobile/LICENCE.rtf --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/project_files/HedgewarsMobile/LICENCE.rtf Thu Feb 09 18:59:46 2012 +0100 @@ -0,0 +1,104 @@ +{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf320 +{\fonttbl\f0\fnil\fcharset0 LucidaGrande;} +{\colortbl;\red255\green255\blue255;\red51\green51\blue51;\red0\green180\blue128;\red255\green0\blue0; +\red31\green105\blue199;\red119\green119\blue119;} +{\*\listtable{\list\listtemplateid1\listhybrid{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{decimal\}.}{\leveltext\leveltemplateid1\'02\'00.;}{\levelnumbers\'01;}\fi-360\li720\lin720 }{\listname ;}\listid1}} +{\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}} +\deftab720 +\pard\pardeftab720\ql\qnatural + +\f0\b\fs24 \cf2 Matt Legend Gemmell / Instinctive Code Source Code License\ + +\b0\fs22 Last updated: 9th May 2010 +\fs24 \ +\ +\ +Thanks for downloading some of our source code!\ +\ +This is the license agreement for the source code which this document accompanies (don\'92t worry: you\'92re allowed to use it in your own products, commercial or otherwise).\ +\ +The full license text is further down this page, and you should only use the source code if you agree to the terms in that text. For convenience, though, we\'92ve put together a human-readable +\b non-authoritative +\b0 interpretation of the license which will hopefully answer any questions you have.\ +\ +\ + +\b \cf3 Green +\b0 \cf2 text shows +\b \cf3 what you can do with the code +\b0 \cf2 .\ + +\b \cf4 Red +\b0 \cf2 text means +\b \cf4 restrictions you must abide by +\b0 \cf2 .\ +\ +Basically, the license says that:\ +\ +\pard\tx220\tx720\pardeftab720\li720\fi-720\ql\qnatural +\ls1\ilvl0\cf2 {\listtext 1. }You can +\b \cf3 use the code in your own products, including commercial and/or closed-source products +\b0 \cf2 .\ +{\listtext 2. }You can +\b \cf3 modify the code +\b0 \cf0 as you wish\cf2 , and +\b \cf3 use the modified code in your products +\b0 \cf2 .\ +{\listtext 3. }You can +\b \cf3 redistribute the original, unmodified code +\b0 \cf2 , but you +\b \cf4 have to include the full license text below +\b0 \cf2 .\ +{\listtext 4. }You can +\b \cf3 redistribute the modified code +\b0 \cf2 as you wish ( +\b \cf4 without the full license text below +\b0 \cf2 ).\ +{\listtext 5. }In all cases, you +\b \cf4 must include a credit mentioning Matt Legend Gemmell +\b0 \cf2 as the original author of the source.\ +{\listtext 6. }Matt Legend Gemmell is \cf0 not liable for anything you do with the code\cf2 , no matter what. So be sensible.\ +{\listtext 7. }You +\b \cf4 can\'92t use the name Matt Legend Gemmell, the name Instinctive Code, the Instinctive Code logo or any other related marks to promote your products +\b0 \cf2 based on the code.\ +{\listtext 8. }If you agree to all of that, go ahead and use the source. Otherwise, don\'92t!\ +\pard\pardeftab720\ql\qnatural +\cf2 \ + +\b \ +\ +Suggested Attribution Format\ + +\b0 \ +The license requires that you give credit to Matt Legend Gemmell, as the original author of any of our source that you use. The placement and format of the credit is up to you, but we prefer the credit to be in the software\'92s \'93About\'94 window. Alternatively, you could put the credit in a list of acknowledgements within the software, in the software\'92s documentation, or on the web page for the software. The suggested format for the attribution is:\ +\ +\pard\pardeftab720\ql\qnatural + +\b \cf0 Includes code by {\field{\*\fldinst{HYPERLINK "http://mattgemmell.com/"}}{\fldrslt \cf5 Matt Legend Gemmell}}\cf6 . +\b0 \ +\pard\pardeftab720\ql\qnatural +\cf2 \ +where would be replaced by the name of the specific source-code package you made use of. Where possible, please link the text \'93Matt Legend Gemmell\'94 to the following URL, or include the URL as plain text: {\field{\*\fldinst{HYPERLINK "http://mattgemmell.com/"}}{\fldrslt \cf5 http://mattgemmell.com/}}\ +\ +\ + +\b Full Source Code License Text\ +\ + +\b0 Below you can find the actual text of the license agreement. +\b \ +\ +\pard\pardeftab720\ql\qnatural +\cf6 \ +License Agreement for Source Code provided by Matt Legend Gemmell +\b0 \ +\ +This software is supplied to you by Matt Legend Gemmell in consideration of your agreement to the following terms, and your use, installation, modification or redistribution of this software constitutes acceptance of these terms. If you do not agree with these terms, please do not use, install, modify or redistribute this software.\ +\ +In consideration of your agreement to abide by the following terms, and subject to these terms, Matt Legend Gemmell grants you a personal, non-exclusive license, to use, reproduce, modify and redistribute the software, with or without modifications, in source and/or binary forms; provided that if you redistribute the software in its entirety and without modifications, you must retain this notice and the following text and disclaimers in all such redistributions of the software, and that in all cases attribution of Matt Legend Gemmell as the original author of the source code shall be included in all such resulting software products or distributions.\uc0\u8232 \ +Neither the name, trademarks, service marks or logos of Matt Legend Gemmell or Instinctive Code may be used to endorse or promote products derived from the software without specific prior written permission from Matt Legend Gemmell. Except as expressly stated in this notice, no other rights or licenses, express or implied, are granted by Matt Legend Gemmell herein, including but not limited to any patent rights that may be infringed by your derivative works or by other works in which the software may be incorporated.\ +\ +The software is provided by Matt Legend Gemmell on an "AS IS" basis. MATT LEGEND GEMMELL AND INSTINCTIVE CODE MAKE NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\ +\ +IN NO EVENT SHALL MATT LEGEND GEMMELL OR INSTINCTIVE CODE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF MATT LEGEND GEMMELL OR INSTINCTIVE CODE HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\ +} \ No newline at end of file diff -r e1125559359f -r 2cccf6b2b89d project_files/HedgewarsMobile/MGSplitCornersView.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/project_files/HedgewarsMobile/MGSplitCornersView.h Thu Feb 09 18:59:46 2012 +0100 @@ -0,0 +1,31 @@ +// +// MGSplitCornersView.h +// MGSplitView +// +// Created by Matt Gemmell on 28/07/2010. +// Copyright 2010 Instinctive Code. +// + +#import + +typedef enum _MGCornersPosition { + MGCornersPositionLeadingVertical = 0, // top of screen for a left/right split. + MGCornersPositionTrailingVertical = 1, // bottom of screen for a left/right split. + MGCornersPositionLeadingHorizontal = 2, // left of screen for a top/bottom split. + MGCornersPositionTrailingHorizontal = 3 // right of screen for a top/bottom split. +} MGCornersPosition; + +@class MGSplitViewController; +@interface MGSplitCornersView : UIView { + float cornerRadius; + MGSplitViewController *splitViewController; + MGCornersPosition cornersPosition; + UIColor *cornerBackgroundColor; +} + +@property (nonatomic, assign) float cornerRadius; +@property (nonatomic, assign) MGSplitViewController *splitViewController; // weak ref. +@property (nonatomic, assign) MGCornersPosition cornersPosition; // don't change this manually; let the splitViewController manage it. +@property (nonatomic, retain) UIColor *cornerBackgroundColor; + +@end diff -r e1125559359f -r 2cccf6b2b89d project_files/HedgewarsMobile/MGSplitCornersView.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/project_files/HedgewarsMobile/MGSplitCornersView.m Thu Feb 09 18:59:46 2012 +0100 @@ -0,0 +1,241 @@ +// +// MGSplitCornersView.m +// MGSplitView +// +// Created by Matt Gemmell on 28/07/2010. +// Copyright 2010 Instinctive Code. +// + +#import "MGSplitCornersView.h" + + +@implementation MGSplitCornersView + + +#pragma mark - +#pragma mark Setup and teardown + + +- (id)initWithFrame:(CGRect)frame +{ + if ((self = [super initWithFrame:frame])) { + self.contentMode = UIViewContentModeRedraw; + self.userInteractionEnabled = NO; + self.opaque = NO; + self.backgroundColor = [UIColor clearColor]; + cornerRadius = 0.0; // actual value is set by the splitViewController. + cornersPosition = MGCornersPositionLeadingVertical; + } + + return self; +} + + +- (void)dealloc +{ + self.cornerBackgroundColor = nil; + + [super dealloc]; +} + + +#pragma mark - +#pragma mark Geometry helpers + + +double deg2Rad(double degrees) +{ + // Converts degrees to radians. + return degrees * (M_PI / 180.0); +} + + +double rad2Deg(double radians) +{ + // Converts radians to degrees. + return radians * (180 / M_PI); +} + + +#pragma mark - +#pragma mark Drawing + + +- (void)drawRect:(CGRect)rect +{ + // Draw two appropriate corners, with cornerBackgroundColor behind them. + if (cornerRadius > 0) { + if (NO) { // just for debugging. + [[UIColor redColor] set]; + UIRectFill(self.bounds); + } + + float maxX = CGRectGetMaxX(self.bounds); + float maxY = CGRectGetMaxY(self.bounds); + UIBezierPath *path = [UIBezierPath bezierPath]; + CGPoint pt = CGPointZero; + switch (cornersPosition) { + case MGCornersPositionLeadingVertical: // top of screen for a left/right split + [path moveToPoint:pt]; + pt.y += cornerRadius; + [path appendPath:[UIBezierPath bezierPathWithArcCenter:pt radius:cornerRadius startAngle:deg2Rad(90) endAngle:0 clockwise:YES]]; + pt.x += cornerRadius; + pt.y -= cornerRadius; + [path addLineToPoint:pt]; + [path addLineToPoint:CGPointZero]; + [path closePath]; + + pt.x = maxX - cornerRadius; + pt.y = 0; + [path moveToPoint:pt]; + pt.y = maxY; + [path addLineToPoint:pt]; + pt.x += cornerRadius; + [path appendPath:[UIBezierPath bezierPathWithArcCenter:pt radius:cornerRadius startAngle:deg2Rad(180) endAngle:deg2Rad(90) clockwise:YES]]; + pt.y -= cornerRadius; + [path addLineToPoint:pt]; + pt.x -= cornerRadius; + [path addLineToPoint:pt]; + [path closePath]; + + break; + + case MGCornersPositionTrailingVertical: // bottom of screen for a left/right split + pt.y = maxY; + [path moveToPoint:pt]; + pt.y -= cornerRadius; + [path appendPath:[UIBezierPath bezierPathWithArcCenter:pt radius:cornerRadius startAngle:deg2Rad(270) endAngle:deg2Rad(360) clockwise:NO]]; + pt.x += cornerRadius; + pt.y += cornerRadius; + [path addLineToPoint:pt]; + pt.x -= cornerRadius; + [path addLineToPoint:pt]; + [path closePath]; + + pt.x = maxX - cornerRadius; + pt.y = maxY; + [path moveToPoint:pt]; + pt.y -= cornerRadius; + [path addLineToPoint:pt]; + pt.x += cornerRadius; + [path appendPath:[UIBezierPath bezierPathWithArcCenter:pt radius:cornerRadius startAngle:deg2Rad(180) endAngle:deg2Rad(270) clockwise:NO]]; + pt.y += cornerRadius; + [path addLineToPoint:pt]; + pt.x -= cornerRadius; + [path addLineToPoint:pt]; + [path closePath]; + + break; + + case MGCornersPositionLeadingHorizontal: // left of screen for a top/bottom split + pt.x = 0; + pt.y = cornerRadius; + [path moveToPoint:pt]; + pt.y -= cornerRadius; + [path addLineToPoint:pt]; + pt.x += cornerRadius; + [path appendPath:[UIBezierPath bezierPathWithArcCenter:pt radius:cornerRadius startAngle:deg2Rad(180) endAngle:deg2Rad(270) clockwise:NO]]; + pt.y += cornerRadius; + [path addLineToPoint:pt]; + pt.x -= cornerRadius; + [path addLineToPoint:pt]; + [path closePath]; + + pt.x = 0; + pt.y = maxY - cornerRadius; + [path moveToPoint:pt]; + pt.y = maxY; + [path addLineToPoint:pt]; + pt.x += cornerRadius; + [path appendPath:[UIBezierPath bezierPathWithArcCenter:pt radius:cornerRadius startAngle:deg2Rad(180) endAngle:deg2Rad(90) clockwise:YES]]; + pt.y -= cornerRadius; + [path addLineToPoint:pt]; + pt.x -= cornerRadius; + [path addLineToPoint:pt]; + [path closePath]; + + break; + + case MGCornersPositionTrailingHorizontal: // right of screen for a top/bottom split + pt.y = cornerRadius; + [path moveToPoint:pt]; + pt.y -= cornerRadius; + [path appendPath:[UIBezierPath bezierPathWithArcCenter:pt radius:cornerRadius startAngle:deg2Rad(270) endAngle:deg2Rad(360) clockwise:NO]]; + pt.x += cornerRadius; + pt.y += cornerRadius; + [path addLineToPoint:pt]; + pt.x -= cornerRadius; + [path addLineToPoint:pt]; + [path closePath]; + + pt.y = maxY - cornerRadius; + [path moveToPoint:pt]; + pt.y += cornerRadius; + [path appendPath:[UIBezierPath bezierPathWithArcCenter:pt radius:cornerRadius startAngle:deg2Rad(90) endAngle:0 clockwise:YES]]; + pt.x += cornerRadius; + pt.y -= cornerRadius; + [path addLineToPoint:pt]; + pt.x -= cornerRadius; + [path addLineToPoint:pt]; + [path closePath]; + + break; + + default: + break; + } + + [self.cornerBackgroundColor set]; + [path fill]; + } +} + + +#pragma mark - +#pragma mark Accessors and properties + + +- (void)setCornerRadius:(float)newRadius +{ + if (newRadius != cornerRadius) { + cornerRadius = newRadius; + [self setNeedsDisplay]; + } +} + + +- (void)setSplitViewController:(MGSplitViewController *)theController +{ + if (theController != splitViewController) { + splitViewController = theController; + [self setNeedsDisplay]; + } +} + + +- (void)setCornersPosition:(MGCornersPosition)posn +{ + if (cornersPosition != posn) { + cornersPosition = posn; + [self setNeedsDisplay]; + } +} + + +- (void)setCornerBackgroundColor:(UIColor *)color +{ + if (color != cornerBackgroundColor) { + [cornerBackgroundColor release]; + cornerBackgroundColor = [color retain]; + [self setNeedsDisplay]; + } +} + + +@synthesize cornerRadius; +@synthesize splitViewController; +@synthesize cornersPosition; +@synthesize cornerBackgroundColor; + + +@end diff -r e1125559359f -r 2cccf6b2b89d project_files/HedgewarsMobile/MGSplitDividerView.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/project_files/HedgewarsMobile/MGSplitDividerView.h Thu Feb 09 18:59:46 2012 +0100 @@ -0,0 +1,22 @@ +// +// MGSplitDividerView.h +// MGSplitView +// +// Created by Matt Gemmell on 26/07/2010. +// Copyright 2010 Instinctive Code. +// + +#import + +@class MGSplitViewController; +@interface MGSplitDividerView : UIView { + MGSplitViewController *splitViewController; + BOOL allowsDragging; +} + +@property (nonatomic, assign) MGSplitViewController *splitViewController; // weak ref. +@property (nonatomic, assign) BOOL allowsDragging; + +- (void)drawGripThumbInRect:(CGRect)rect; + +@end diff -r e1125559359f -r 2cccf6b2b89d project_files/HedgewarsMobile/MGSplitDividerView.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/project_files/HedgewarsMobile/MGSplitDividerView.m Thu Feb 09 18:59:46 2012 +0100 @@ -0,0 +1,216 @@ +// +// MGSplitDividerView.m +// MGSplitView +// +// Created by Matt Gemmell on 26/07/2010. +// Copyright 2010 Instinctive Code. +// + +#import "MGSplitDividerView.h" +#import "MGSplitViewController.h" + + +@implementation MGSplitDividerView + + +#pragma mark - +#pragma mark Setup and teardown + + +- (id)initWithFrame:(CGRect)frame +{ + if ((self = [super initWithFrame:frame])) { + self.userInteractionEnabled = NO; + self.allowsDragging = NO; + self.contentMode = UIViewContentModeRedraw; + } + return self; +} + + +- (void)dealloc +{ + self.splitViewController = nil; + [super dealloc]; +} + + +#pragma mark - +#pragma mark Drawing + + +- (void)drawRect:(CGRect)rect +{ + if (splitViewController.dividerStyle == MGSplitViewDividerStyleThin) { + [super drawRect:rect]; + + } else if (splitViewController.dividerStyle == MGSplitViewDividerStylePaneSplitter) { + // Draw gradient background. + CGRect bounds = self.bounds; + CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB(); + CGFloat locations[2] = {0, 1}; + CGFloat components[8] = { 0.988, 0.988, 0.988, 1.0, // light + 0.875, 0.875, 0.875, 1.0 };// dark + CGGradientRef gradient = CGGradientCreateWithColorComponents (rgb, components, locations, 2); + CGContextRef context = UIGraphicsGetCurrentContext(); + CGPoint start, end; + if (splitViewController.vertical) { + // Light left to dark right. + start = CGPointMake(CGRectGetMinX(bounds), CGRectGetMidY(bounds)); + end = CGPointMake(CGRectGetMaxX(bounds), CGRectGetMidY(bounds)); + } else { + // Light top to dark bottom. + start = CGPointMake(CGRectGetMidX(bounds), CGRectGetMinY(bounds)); + end = CGPointMake(CGRectGetMidX(bounds), CGRectGetMaxY(bounds)); + } + CGContextDrawLinearGradient(context, gradient, start, end, 0); + CGColorSpaceRelease(rgb); + CGGradientRelease(gradient); + + // Draw borders. + float borderThickness = 1.0; + [[UIColor colorWithWhite:0.7 alpha:1.0] set]; + CGRect borderRect = bounds; + if (splitViewController.vertical) { + borderRect.size.width = borderThickness; + UIRectFill(borderRect); + borderRect.origin.x = CGRectGetMaxX(bounds) - borderThickness; + UIRectFill(borderRect); + + } else { + borderRect.size.height = borderThickness; + UIRectFill(borderRect); + borderRect.origin.y = CGRectGetMaxY(bounds) - borderThickness; + UIRectFill(borderRect); + } + + // Draw grip. + [self drawGripThumbInRect:bounds]; + } +} + + +- (void)drawGripThumbInRect:(CGRect)rect +{ + float width = 9.0; + float height; + if (splitViewController.vertical) { + height = 30.0; + } else { + height = width; + width = 30.0; + } + + // Draw grip in centred in rect. + CGRect gripRect = CGRectMake(0, 0, width, height); + gripRect.origin.x = ((rect.size.width - gripRect.size.width) / 2.0); + gripRect.origin.y = ((rect.size.height - gripRect.size.height) / 2.0); + + float stripThickness = 1.0; + UIColor *stripColor = [UIColor colorWithWhite:0.35 alpha:1.0]; + UIColor *lightColor = [UIColor colorWithWhite:1.0 alpha:1.0]; + float space = 3.0; + if (splitViewController.vertical) { + gripRect.size.width = stripThickness; + [stripColor set]; + UIRectFill(gripRect); + + gripRect.origin.x += stripThickness; + gripRect.origin.y += 1; + [lightColor set]; + UIRectFill(gripRect); + gripRect.origin.x -= stripThickness; + gripRect.origin.y -= 1; + + gripRect.origin.x += space + stripThickness; + [stripColor set]; + UIRectFill(gripRect); + + gripRect.origin.x += stripThickness; + gripRect.origin.y += 1; + [lightColor set]; + UIRectFill(gripRect); + gripRect.origin.x -= stripThickness; + gripRect.origin.y -= 1; + + gripRect.origin.x += space + stripThickness; + [stripColor set]; + UIRectFill(gripRect); + + gripRect.origin.x += stripThickness; + gripRect.origin.y += 1; + [lightColor set]; + UIRectFill(gripRect); + + } else { + gripRect.size.height = stripThickness; + [stripColor set]; + UIRectFill(gripRect); + + gripRect.origin.y += stripThickness; + gripRect.origin.x -= 1; + [lightColor set]; + UIRectFill(gripRect); + gripRect.origin.y -= stripThickness; + gripRect.origin.x += 1; + + gripRect.origin.y += space + stripThickness; + [stripColor set]; + UIRectFill(gripRect); + + gripRect.origin.y += stripThickness; + gripRect.origin.x -= 1; + [lightColor set]; + UIRectFill(gripRect); + gripRect.origin.y -= stripThickness; + gripRect.origin.x += 1; + + gripRect.origin.y += space + stripThickness; + [stripColor set]; + UIRectFill(gripRect); + + gripRect.origin.y += stripThickness; + gripRect.origin.x -= 1; + [lightColor set]; + UIRectFill(gripRect); + } +} + + +#pragma mark - +#pragma mark Interaction + + +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event +{ + UITouch *touch = [touches anyObject]; + if (touch) { + 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; + } +} + + +#pragma mark - +#pragma mark Accessors and properties + + +- (void)setAllowsDragging:(BOOL)flag +{ + if (flag != allowsDragging) { + allowsDragging = flag; + self.userInteractionEnabled = allowsDragging; + } +} + + +@synthesize splitViewController; +@synthesize allowsDragging; + + +@end diff -r e1125559359f -r 2cccf6b2b89d project_files/HedgewarsMobile/MGSplitViewController.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/project_files/HedgewarsMobile/MGSplitViewController.h Thu Feb 09 18:59:46 2012 +0100 @@ -0,0 +1,116 @@ +// +// MGSplitViewController.h +// MGSplitView +// +// Created by Matt Gemmell on 26/07/2010. +// Copyright 2010 Instinctive Code. +// + +#import + +typedef enum _MGSplitViewDividerStyle { + // These names have been chosen to be conceptually similar to those of NSSplitView on Mac OS X. + MGSplitViewDividerStyleThin = 0, // Thin divider, like UISplitViewController (default). + MGSplitViewDividerStylePaneSplitter = 1 // Thick divider, drawn with a grey gradient and a grab-strip. +} MGSplitViewDividerStyle; + +@class MGSplitDividerView; +@protocol MGSplitViewControllerDelegate; +@interface MGSplitViewController : UIViewController { + BOOL _showsMasterInPortrait; + BOOL _showsMasterInLandscape; + 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. + MGSplitDividerView *_dividerView; // View that draws the divider between the master and detail views. + NSArray *_cornerViews; // Views to draw the inner rounded corners between master and detail views. + float _splitPosition; + BOOL _reconfigurePopup; + MGSplitViewDividerStyle _dividerStyle; // Meta-setting which configures several aspects of appearance and behaviour. +} + +@property (nonatomic, assign) IBOutlet id delegate; +@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, 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). + +@property (nonatomic, copy) NSArray *viewControllers; // array of UIViewControllers; master is at index 0, detail is at index 1. +@property (nonatomic, retain) IBOutlet UIViewController *masterViewController; // convenience. +@property (nonatomic, retain) IBOutlet UIViewController *detailViewController; // convenience. +@property (nonatomic, retain) MGSplitDividerView *dividerView; // the view which draws the divider/split between master and detail. +@property (nonatomic, assign) MGSplitViewDividerStyle dividerStyle; // style (and behaviour) of the divider between master and detail. + +@property (nonatomic, readonly, getter=isLandscape) BOOL landscape; // returns YES if this view controller is in either of the two Landscape orientations, else NO. + +// 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; +/* + -cornerViews returns an NSArray of two MGSplitCornersView objects, used to draw the inner corners. + The first view is the "leading" corners (top edge of screen for left/right split, left edge of screen for top/bottom split). + The second view is the "trailing" corners (bottom edge of screen for left/right split, right edge of screen for top/bottom split). + Do NOT modify them, except to: + 1. Change their .cornerBackgroundColor + 2. Change their .cornerRadius + */ + +@end + + +@protocol MGSplitViewControllerDelegate + +@optional + +// Called when a button should be added to a toolbar for a hidden view controller. +- (void)splitViewController:(MGSplitViewController*)svc + willHideViewController:(UIViewController *)aViewController + withBarButtonItem:(UIBarButtonItem*)barButtonItem + forPopoverController: (UIPopoverController*)pc; + +// Called when the master view is shown again in the split view, invalidating the button and popover controller. +- (void)splitViewController:(MGSplitViewController*)svc + willShowViewController:(UIViewController *)aViewController + invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem; + +// Called when the master view is shown in a popover, so the delegate can take action like hiding other popovers. +- (void)splitViewController:(MGSplitViewController*)svc + popoverController:(UIPopoverController*)pc + willPresentViewController:(UIViewController *)aViewController; + +// Called when the split orientation will change (from vertical to horizontal, or vice versa). +- (void)splitViewController:(MGSplitViewController*)svc willChangeSplitOrientationToVertical:(BOOL)isVertical; + +// Called when split position will change to the given pixel value (relative to left if split is vertical, or to top if horizontal). +- (void)splitViewController:(MGSplitViewController*)svc willMoveSplitToPosition:(float)position; + +// Called before split position is changed to the given pixel value (relative to left if split is vertical, or to top if horizontal). +// Note that viewSize is the current size of the entire split-view; i.e. the area enclosing the master, divider and detail views. +- (float)splitViewController:(MGSplitViewController *)svc constrainSplitPosition:(float)proposedPosition splitViewSize:(CGSize)viewSize; + +@end 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