46
votes

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.

16
Why not try Matt Gemmell's excellent MGSplitViewController? It's open source, and is available on GitHub. Gregor, SwedenGregor

16 Answers

65
votes

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];
    }
}
36
votes

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.

22
votes

this code is work for me

[splitViewController setValue:[NSNumber numberWithFloat:200.0] forKey:@"_masterColumnWidth"];
12
votes

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.

10
votes

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.

8
votes

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
6
votes

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.

5
votes

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!

2
votes

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;

2
votes

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

2
votes

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; 
0
votes

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.

0
votes

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;

     }
0
votes

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)
0
votes
// 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];
}
-3
votes

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)