47

The iPad programming guide says that the splitView's left pane is fixed to 320 points. But 320 pixels for my master view controller is too much. I would like to reduce it and give more space to detail view controller. Is it possible by anyway?

Link to the document which speaks about fixed width.

Antoine
  • 23,526
  • 11
  • 88
  • 94
Raj Pawan Gumdal
  • 7,390
  • 10
  • 60
  • 92
  • Why not try Matt Gemmell's excellent [MGSplitViewController](http://mattgemmell.com/2010/08/03/mgsplitviewcontroller-updated)? It's open source, and is available on [GitHub](http://github.com/mattgemmell/MGSplitViewController/). Gregor, Sweden – Gregor Oct 25 '10 at 19:03

16 Answers16

65

If you subclass UISplitViewController, you can implement -viewDidLayoutSubviews and adjust the width there. This is clean, no hacks or private APIs, and works even with rotation.

- (void)viewDidLayoutSubviews
{
    const CGFloat kMasterViewWidth = 240.0;

    UIViewController *masterViewController = [self.viewControllers objectAtIndex:0];
    UIViewController *detailViewController = [self.viewControllers objectAtIndex:1];

    if (detailViewController.view.frame.origin.x > 0.0) {
        // Adjust the width of the master view
        CGRect masterViewFrame = masterViewController.view.frame;
        CGFloat deltaX = masterViewFrame.size.width - kMasterViewWidth;
        masterViewFrame.size.width -= deltaX;
        masterViewController.view.frame = masterViewFrame;

        // Adjust the width of the detail view
        CGRect detailViewFrame = detailViewController.view.frame;
        detailViewFrame.origin.x -= deltaX;
        detailViewFrame.size.width += deltaX;
        detailViewController.view.frame = detailViewFrame;

        [masterViewController.view setNeedsLayout];
        [detailViewController.view setNeedsLayout];
    }
}
jrc
  • 20,354
  • 10
  • 69
  • 64
  • Would this cause the app to be rejected at the AppStore? – Cutetare Nov 27 '13 at 05:51
  • 3
    I don't see why it would, as no private APIs are used. – jrc Dec 01 '13 at 00:43
  • This worked great for me in landscape but when I went back to portrait the view was shifted left. Any ideas how to resolve that? – Charlie S Dec 14 '13 at 16:39
  • I can correctly resize the master view, however when the detailViewController.view.frame gets a new value, the app just goes purely black. Any idea? – nigong Feb 07 '14 at 08:01
  • 3
    This should be the accepted answer. It's ridiculous that Apple makes you jump through hoops like this when they could easily expose a setting, but I'm glad you shared this. – d512 Apr 14 '14 at 04:27
  • @nigong I'm getting the same problem. It somehow causes an infinite loop that I cannot get out of. If I don't set detailViewController.view.frame = detailViewFrame, then the masterViewController.view.frame updates correctly, but the detail doesn't. If I uncomment it, I get an infinite loop :( – lordB8r Jul 24 '14 at 20:27
  • 1
    did you got any solution for the infinite loop? @AndréCytryn – DaSilva Sep 30 '14 at 13:57
  • yes. on my subclass I added: `self.preferredPrimaryColumnWidthFraction = 0.3` but the thing is that I does not go more than 0.4 I dont know why – Andre Cytryn Sep 30 '14 at 15:37
  • 3
    The black screen only happens on iOS 8. On iOS 8, you don't need this code - use `maximumPrimaryColumnWidth` instead. On iOS 7, the black screen doesn't occur and @jrc's solution works. – Bill Oct 16 '14 at 20:45
  • 5
    INFINITE LOOP AVOIDANCE: Some change in iOS8 is what causes the infinite loop. To avoid it, we used the above code, but wrapped it in an if() block that checked for the version of iOS. If it's less than 8.0, then use the above code, else, just set: self.maximumPrimaryColumnWidth = masterViewWidth; Please go upvote @SJTriggs and Twan's answers for this solution. – Kenny Wyland Nov 25 '14 at 00:53
  • Infinite loop is obviously very easy to avoid (test whether you already have the width you're after and only change it if you don't), but as Bill pointed out above: use `maximumPrimaryColumnWidth` on iOS8 and if you still support iOS7, then follow jrc's solution (although I would still improve that with a check of whether you need to adjust the width or not). – Erik van der Neut Apr 15 '15 at 07:11
  • This approach mostly works (+1) but introduces a secondary problem. There is a transparent view used to detect touches outside the master area and instigate closure of the master. If this view's frame isn't updated to be the same as the detail view's frame, it becomes possible for the user to interact with the details whilst the master is open. To fix this, I did this: `this.View.Subviews[2].Frame = detailFrame;` straight after setting the detail frame. – Kent Boogaart Jun 26 '15 at 05:03
36

In IOS 8.0 you can easily do this by doing the following:

1. In your MasterSplitViewController.h add

@property(nonatomic, assign) CGFloat maximumPrimaryColumnWidth NS_AVAILABLE_IOS(8_0);

2. In your MasterSplitViewController.m viewDidLoad method add

 self.maximumPrimaryColumnWidth = 100;
 self.splitViewController.maximumPrimaryColumnWidth = self.maximumPrimaryColumnWidth;

This is a really good, simple and easy feature of IOS 8.

SJTriggs
  • 476
  • 5
  • 13
  • Would anyone be able to provide me the Swift equivalent of this code please? I can't seem to get this working in a Swift project. – romiem Nov 27 '14 at 11:19
  • Never mind, I managed to find the solution (although it is a bit different) http://stackoverflow.com/questions/2949067/change-the-width-of-master-in-uisplitviewcontroller#27191491 – romiem Nov 28 '14 at 15:14
  • 3
    Used with iOS 10 GM, xCode 8 and Swift 2.3. It works like a charm! :) – JJack_ Sep 09 '16 at 18:57
22

this code is work for me

[splitViewController setValue:[NSNumber numberWithFloat:200.0] forKey:@"_masterColumnWidth"];
Maulik
  • 19,348
  • 14
  • 82
  • 137
priyanka
  • 2,076
  • 1
  • 16
  • 20
11

No.


There are two private properties

@property(access,nonatomic) CGFloat masterColumnWidth;
@property(access,nonatomic) CGFloat leftColumnWidth; // both are the same!

but being private mean they can't be used for AppStore apps.

kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
  • 2
    Thanks, but I am not going to use the private APIs, rather I would implement own split view controller . . . – Raj Pawan Gumdal Jun 01 '10 at 11:45
  • @Raj how could I subclass UISplitViewController and only override the masterColumnWidth? – Kyle Clegg Dec 18 '12 at 01:15
  • @Kyle - Cannot go with the above approach, if you want to submit the app to appstore. Otherwise, if you still insist using it, you dont have to subclass it. See this answer below: http://stackoverflow.com/a/4022406/260665 – Raj Pawan Gumdal Dec 19 '12 at 07:05
  • @Raj - I need to submit to the App Store. Is there a way to use the answer you suggested in a fashion that Apple will accept? I am assuming the answer is a subclass, or is that bad too? – Kyle Clegg Dec 19 '12 at 22:02
  • 1
    No no, I am changing the accepted answer for posterity sake. You cannot use this approach or even subclass and change the width if you want to submit to appstore. It will be rejected for sure. Use Matt Gemmell's split view controller. See my new accepted answer. – Raj Pawan Gumdal Dec 20 '12 at 05:46
10

iOS 8 introduced a new property:

// An animatable property that can be used to adjust the maximum absolute width of the primary view controller in the split view controller.
@property(nonatomic, assign) CGFloat maximumPrimaryColumnWidth NS_AVAILABLE_IOS(8_0); // default: UISplitViewControllerAutomaticDimension

Use this property to adjust your master viewcontroller to your desired width.

Antoine
  • 23,526
  • 11
  • 88
  • 94
  • 2
    Since there are many different screen sizes you are better using preferredPrimaryColumnWidthFraction – malhal Jan 03 '16 at 17:36
8

Here is how I did this in iOS8 with Swift.

class MainSplitViewController: UISplitViewController {

    override func viewDidLoad() {

        self.preferredDisplayMode = UISplitViewControllerDisplayMode.AllVisible
        self.maximumPrimaryColumnWidth = 100 // specify your width here
    }
}

If you need to change the width dynamically from within your master/detail view in the split view, then do something like this:

var splitViewController = self.splitViewController as MainSplitViewController
splitViewController.maximumPrimaryColumnWidth = 400
romiem
  • 8,360
  • 7
  • 29
  • 36
6

The storyboard way would be this one, mentioned by @Tim:

enter image description here

Furthermore, if you want the Master view to always take up a certain percentage of the screen then you can use the Key Path = "preferredPrimaryColumnWidthFraction" instead and set the value to 0.2 (for 20% screen size).

Please note that the "maximumPrimaryColumnWidth" is set to 320, so if you try the screen percent value of 0.5 (50%) it won't go above 320. You can add a key path for maximumPrimaryColumnWidth if you need to override this.

Travis M.
  • 10,930
  • 1
  • 56
  • 72
5

None of the answers worked for me on iOS7, so I did some of my own research and created a working solution. This will involve subclassing UISplitViewController for the full functionality.

I will present the answer as if we just created a new project for iPad with all device orientations and have set the custom UISplitViewController as the main view controller.

Create your custom UISplitViewController. In this example mine is called MySplitViewController. All code will be based in MySplitViewController.m.

We're going to need to access a method from the UISplitViewControllerDelegate so add that and set the delegate. We'll also setup a delegate forwarder incase you need to call the delegate methods from another class.

@interface MySplitViewController () <UISplitViewControllerDelegate>
@property (nonatomic, weak) id<UISplitViewControllerDelegate> realDelegate;
@end

@implementation MySplitViewController

- (instancetype)init {
    self = [super init];
    if (self) {
        self.delegate = self;
    }
    return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if (self) {
        self.delegate = self;
    }
    return self;
}

- (void)setDelegate:(id<UISplitViewControllerDelegate>)delegate {
    [super setDelegate:nil];
    self.realDelegate = (delegate != self) ? delegate : nil;
    [super setDelegate:delegate ? self : nil];
}

- (BOOL)respondsToSelector:(SEL)aSelector {
    id delegate = self.realDelegate;
    return [super respondsToSelector:aSelector] || [delegate respondsToSelector:aSelector];
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    id delegate = self.realDelegate;
    return [delegate respondsToSelector:aSelector] ? delegate : [super forwardingTargetForSelector:aSelector];
}

Setup the master and detail view controllers.

- (void)viewDidLoad {
    [super viewDidLoad];

    UIViewController* masterViewController = [[UIViewController alloc] init];
    masterViewController.view.backgroundColor = [UIColor yellowColor];

    UIViewController* detailViewController = [[UIViewController alloc] init];
    detailViewController.view.backgroundColor = [UIColor cyanColor];

    self.viewControllers = @[masterViewController, detailViewController];
}

Lets add our desired width to a method for easy reference.

- (CGFloat)desiredWidth {
    return 200.0f;
}

We'll manipulate the master view controller before presenting it.

- (void)splitViewController:(UISplitViewController *)svc popoverController:(UIPopoverController *)pc willPresentViewController:(UIViewController *)aViewController {
    id realDelegate = self.realDelegate;

    if ([realDelegate respondsToSelector:@selector(splitViewController:popoverController:willPresentViewController:)]) {
        [realDelegate splitViewController:svc popoverController:pc willPresentViewController:aViewController];
    }

    CGRect rect = aViewController.view.frame;
    rect.size.width = [self desiredWidth];
    aViewController.view.frame = rect;

    aViewController.view.superview.clipsToBounds = NO;
}

However, now we're left with a display like this. enter image description here

So were going to override a private method. Yes a private method, it will still be acceptable in the App Store since its not an underscore private method.

- (CGFloat)leftColumnWidth {
    return [self desiredWidth];
}

This deals with portrait mode. So a similar thing for -splitViewController:willShowViewController:invalidatingBarButtonItem: and you should be set for landscape.

However none of this will be needed in iOS8. You'll be able to simply call a min and max width property!

cnotethegr8
  • 7,342
  • 8
  • 68
  • 104
2

use the following code before assigning to the rootviewcontroller. It works for me with ios7

[self.splitViewController setValue:[NSNumber numberWithFloat:256.0] forKey:@"_masterColumnWidth"]; self.window.rootViewController = self.splitViewController;

xevser
  • 369
  • 4
  • 5
2

Since no one mentioned that this can be done from IB, I want to add this answer. Apparently, you can set "User Defined Runtime Attributes" for the UISplitViewContorller with following details: Key Path:masterColumnWidth Type: Number Value: 250

Tim
  • 21
  • 2
2

In my case, I had to set both maximum and minimum to make this work

mySplitViewController.preferredDisplayMode = .allVisible;
mySplitViewController.maximumPrimaryColumnWidth = UIScreen.main.bounds.width/2;
mySplitViewController.minimumPrimaryColumnWidth = UIScreen.main.bounds.width/2; 
grep
  • 566
  • 5
  • 20
0

You can use GSSplitViewController. This one will work on iOS 7 and 8

splitView = [[GSSplitViewController alloc] init];
splitView.masterPaneWidth = 180;

You can also include it by adding pod 'GSSplitViewController' to your Podfile.

o.akrout
  • 399
  • 5
  • 13
0

ViewController.h @property(nonatomic, assign) CGFloat maximumPrimaryColumnWidth NS_AVAILABLE_IOS(8_0);

ViewController.m

#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)

    if (SYSTEM_VERSION_LESS_THAN(@"10.0")) {

    [self setValue:[NSNumber numberWithFloat:200.0]forKey:@"_masterColumnWidth"];

    }else{

    self.maximumPrimaryColumnWidth = 200;
    self.splitViewController.maximumPrimaryColumnWidth = self.maximumPrimaryColumnWidth;

     }
Himanshu jamnani
  • 316
  • 2
  • 10
0

Swift 3.0 you use like

let widthfraction = 2.0 //Your desired value for me 2.0    
splitViewController?.preferredPrimaryColumnWidthFraction = 0.40
let minimumWidth = min((splitViewController?.view.bounds.size.width)!,(splitViewController?.view.bounds.height)!)
splitViewController?.minimumPrimaryColumnWidth = minimumWidth / widthFraction
splitViewController?.maximumPrimaryColumnWidth = minimumWidth / widthFraction
let leftNavController = splitViewController?.viewControllers.first as! UINavigationController
leftNavController.view.frame = CGRect(x: leftNavController.view.frame.origin.x, y: leftNavController.view.frame.origin.y, width: (minimumWidth / widthFraction), height: leftNavController.view.frame.height)
Madasamy
  • 67
  • 7
0
// in UISplitViewController subclass
// let more space for detail in portrait mode
- (void)viewWillLayoutSubviews
{
    CGFloat width;
    if (UIInterfaceOrientationIsPortrait(UIApplication.sharedApplication.statusBarOrientation)){
        width = CGRectGetWidth(self.view.bounds) * 0.25f;
    }
    else {
        width = CGRectGetWidth(self.view.bounds) * 0.33f;
    }
    width = (NSInteger)fminf(260, fmaxf(120, width));
    self.minimumPrimaryColumnWidth = width;
    self.maximumPrimaryColumnWidth = width;

    [super viewWillLayoutSubviews];
}
Roman Solodyashkin
  • 799
  • 12
  • 17
-3

This code work for me:)

@interface UISplitViewController(myExt) 
- (void)setNewMasterSize:(float)size; 
@end

@implementation UISplitViewController(myExt) - (void)setNewMasterSize:(float)size { _masterColumnWidth = size; } @end

and use it on each operation with view (like rotation)

Kames
  • 3
  • 2
  • I know that privates can be modified using categories like this one, but this is the second time I try it and getting the same error:"_OBJC_IVAR_$_UISplitViewController._masterColumnWidth", referenced from: _OBJC_IVAR_$_UISplitViewController._masterColumnWidth$non_lazy_ptr in UISplitViewController+myExt.o (maybe you meant: _OBJC_IVAR_$_UISplitViewController._masterColumnWidth$non_lazy_ptr) Symbol(s) not found. Collect2: ld returned 1 exit status – nacho4d Aug 26 '10 at 08:24