As it currently stands, this question is not a good fit for our Q&A format. We expect answers to be supported by facts, references, or expertise, but this question will likely solicit debate, arguments, polling, or extended discussion. If you feel that this question can be improved and possibly reopened, visit the help center for guidance.
Closed 10 years ago.
I am just about to start a new project where I have a custom menu that I need to display on everyview that I have. I dont want to use tab bars as this menu is custom designed and may have some animation added to it at some point.
Is there a simple way of creating this menu in one place so that I dont have to build it into every xib file??
Thanks
The tab bar controller is a system provided container controller. If you're using iOS 5 and later, you can make your own custom container view controller:
See Custom Container View Controllers discussion in the View Controller Programming Guide.
The key methods you need are enumerated in the UIViewController Class Reference, too.
I'd also suggest checking out WWDC 2011 #102 - Implementing UIViewController Containment.
Update:
If you want to write your own custom menu, you could do something like the following. I'm not doing anything fancy, but I'm just adding three colored subviews that might correspond to your custom buttons. And I have a tap gesture recognizer on each, which you can obviously handle as you see fit:
NSInteger const kHeight = 50;
NSInteger const kCount = 3;
#interface CustomMenu ()
#property (nonatomic, strong) NSMutableArray *menuViews;
#end
#implementation CustomMenu
- (id)init
{
self = [super init];
if (self)
{
_menuViews = [[NSMutableArray alloc] init];
for (NSInteger i = 0; i < kCount; i++)
{
UIView *subview = [[UIView alloc] init];
subview.tag = i;
[self addSubview:subview];
[_menuViews addObject:subview];
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTap:)];
[subview addGestureRecognizer:recognizer];
}
[_menuViews[0] setBackgroundColor:[UIColor blueColor]];
[_menuViews[1] setBackgroundColor:[UIColor redColor]];
[_menuViews[2] setBackgroundColor:[UIColor greenColor]];
}
return self;
}
- (void)layoutSubviews
{
CGFloat width = self.superview.bounds.size.width;
CGFloat height = self.superview.bounds.size.height;
CGFloat menuChoiceWidth = width / kCount;
self.frame = CGRectMake(0, height - kHeight, width, kHeight);
NSInteger subviewIndex = 0;
for (UIView *subview in self.menuViews)
{
subview.frame = CGRectMake(subviewIndex * menuChoiceWidth, 0,
menuChoiceWidth, kHeight);
subviewIndex++;
}
}
- (void)handleTap:(UITapGestureRecognizer *)recognizer
{
NSLog(#"%s tapped on %d", __FUNCTION__, recognizer.view.tag);
}
#end
Then, you various view controllers just need to make sure to add the CustomMenu to the view:
#interface ViewController ()
#property (nonatomic, strong) CustomMenu *menu;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.menu = [[CustomMenu alloc] init];
[self.view addSubview:self.menu];
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[self.menu layoutSubviews];
}
#end
I confess that I've given up on iOS 4.3 support (it just isn't worth the heartache and the size of the 4.3 audience is pretty small nowadays), so I don't deal with this silliness any more, but hopefully this gives you a sense of what one possible solution might look like.
Related
I'm try to return a UITextField and a UIView (A rectangle box) at the same time so I can have a text field inside a rectangle UIView coloured box, but I can only return one value at a time. Is it possible to return 2 values? Or can I edit a text field to have a rectangle coloured background? Also, the UITextField is being called programatically, not from the story board.
Any kind of help is greatly appreciated. Thank you.
you can return two values with help of block.
Please find code below it may be helpful to you.
- (void)getUIControlles:(void (^)(UITextField * objTextFiled, UIView * objView))completionBlock {
UITextField * textFiled = nil;
/*
do code here for textfiled
*/
UIView * viewDemo = nil;
/*
do code here for Uiview.
*/
completionBlock (textFiled, viewDemo);
}
- (void) testMethod {
// Call function with following way.
[self getUIControlles:^(UITextField *objTextFiled, UIView *objView) {
// objTextFiled = This is your textfiled object
// objView = This is your view object
}];
}
The common way to return multiple independent values in C, C++, and Objective-C is through pointers:
#interface MyController (UIViewController)
- (void)getView:(UIView **)viewOut textField:(UITextField **)textFieldOut;
#end
#implementation MyController
- (void)getView:(UIView **)view textField:(UITextField **)textField {
UIView *view = [[UIView alloc] init];
// ... initialize view
UITextField *textField = [[UITextField alloc] init];
[view addSubview:textField];
// ... initialize textField
*viewOut = view;
*textFieldOut = textField;
}
#end
Apple uses this pattern, for example in +[NSStream getStreamsToHostWithName:port:inputStream:outputStream:] (which is not in the documentation but is in the header files).
Example use:
UIView *view;
UITextField *textField;
[myController getView:&view textField:&textField];
[myController.view addSubview:view];
Another approach is to return one object directly and the other through a pointer:
- (UITextField *)newTextFieldWithWrapperView:(UIView **)viewOut {
UIView *view = [[UIView alloc] init];
// ... initialize view
UITextField *textField = [[UITextField alloc] init];
[view addSubview:textField];
// ... initialize textField
*viewOut = view;
return textField;
}
Apple uses this pattern, for example in -[NSAttributedString initWithFileURL:options:documentAttributes:error:], which returns the string directly, and optionally returns a document attributes dictionary and an error object through pointers.
Well I've never programming in Objective C but if you insist on returning 2 values, create a Pair class an return that.
I'm trying to understand how to make a duplicate of a uiview that has a set of uibuttons inside it.
Been trying to follow this question/answer but I'm really confused atm:
Make a deep copy of a UIView and all its subviews
Basically trying to make a vc that displays two sets of uiviews with buttons. This is what the regular view will look like:
Points of team 1:
+ + + +
1 2 3 P
- - -
Points of team 2:
+ + + +
1 2 3 P
- - -
And I need to make a copy of it. I can probably just drag objects onto the viewcontroller but it'll have way too many IBactions for it if I create another copy.
Thoughts on how to deal with this?
EDIT:
This is how I solved adding multiple buttons
Add a multiple buttons to a view programmatically, call the same method, determine which button it was
First I would create a UIView subclass called PointsView or something.
This will look like this...
Points of [name label]:
+ + + +
1 2 3 P
- - -
It will have properties like NSString *teamName and set those properties up against the relevant labels.
It may also have properties for NSUInteger score so you can set the score value of the PointView object.
This is all completely separate from your UIViewController.
Now, in your UIViewController subclass you can do something like...
PointsView *view1 = [[PointsView alloc] initWithFrame:view1Frame];
view1.teamName = #"Team 1";
view1.score1 = 1;
view1.score2 = 2;
view1.score3 = 3;
[self.view addSubView:view1];
PointsView *view2 = [[PointsView alloc] initWithFrame:view2Frame];
view2.teamName = #"Team 2";
view2.score1 = 1;
view2.score2 = 2;
view2.score3 = 3;
[self.view addSubView:view2];
Now there is not copying involved. You just create two instances of an object.
EDIT
Creating your view subclass...
The easiest way to create your view subclass is to do the following...
Create the files... PointsView.m and PointsView.h
The .h file will look something like this...
#import <UIKit/UIKit.h>
#interface PointsView : UIView
#property (nonatomic, strong) UILabel *teamNameLabel;
// other properties go here...
#end
The .m will look like this...
#import "PointsView.h"
#implementation PointsView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.teamNameLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 100, 21)];
self.teamNameLabel.backgroundColor = [UIColor clearColor];
[self addSubView:self.teamNameLabel];
// set up other UI elements here...
}
return self;
}
#end
Then in your view controller you add the PointsView to it IN CODE (i.e. not with Interface builder) like this...
- (void)viewDidLoad
{
[super viewDidLoad];
PointsView *pointsView1 = [[PointsView alloc] initWithFrame:CGRectMake(0, 0, 320, 200)];
pointsView1.teamNameLabel.text = #"Team 1";
[self.view addSubView:pointsView1];
// add the second one here...
}
You can also create and add these views in Interface Builder but it's a lot harder to explain on here.
If you set it up this way then you can use IB for setting up the rest of your UIViewController. Just don't use IB to set up the PointsViews. It won't work with the way I've shown here.
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question does not appear to be about programming within the scope defined in the help center.
Closed 5 years ago.
Improve this question
I'm preparing to submit such a customization by subclassing UIAlerView. It's layout is entirely based on the given topography of the UIAlertView, have no read any private property. Is this kind of customization acceptable by App Store review process?
BGAlertViewWithSwitch.h
//
// BGAlertViewWithSwitch.h
// BGAlertViewWithSwitch
//
// Created by Borbas Geri on 11/7/11.
// Copyright 2011 ©ompactApps. All rights reserved.
//
#import <Foundation/Foundation.h>
//An assumed value.
#define ALERT_VIEW_LINE_HEIGHT 20.0
#define ALERT_VIEW_LABEL_PADDING 5.0
#define ALERT_VIEW_LABEL_ALPHA 0.5
#define kAlertSwitchLabelTag 42
#interface BGAlertViewWithSwitch : UIAlertView
{
UISwitch *_alertSwitch;
UILabel *_alertSwitchLabel;
}
#property (nonatomic, retain) UISwitch *alertSwitch;
#property (nonatomic, retain) UILabel *alertSwitchLabel;
#property (nonatomic, readonly, getter=isOn) BOOL on;
-(id)initWithTitle:(NSString*) title
message:(NSString*) message
switchMessage:(NSString*) switchMessage
delegate:(id) delegate
cancelButtonTitle:(NSString*) cancelButtonTitle
okButtonTitle:(NSString*) okButtonTitle;
#end
BGAlertViewWithSwitch.m
//
// BGAlertViewWithSwitch.m
// BGAlertViewWithSwitch
//
// Created by Borbas Geri on 11/7/11.
// Copyright 2011 ©ompactApps. All rights reserved.
//
#import "BGAlertViewWithSwitch.h"
#implementation BGAlertViewWithSwitch
#synthesize alertSwitch = _alertSwitch;
#synthesize alertSwitchLabel = _alertSwitchLabel;
#pragma mark - UISwitch Accessor
-(BOOL)isOn
{
return self.alertSwitch.isOn;
}
#pragma mark - View lifecycle
-(id)initWithTitle:(NSString*) title
message:(NSString*) message
switchMessage:(NSString*) switchMessage
delegate:(id) delegate
cancelButtonTitle:(NSString*) cancelButtonTitle
okButtonTitle:(NSString*) okButtonTitle
{
//For testing layout
NSString *placeHolder = #"";
//Append a line to the message that leaves the place for the switch.
NSString *_expandedMessage = [NSString stringWithFormat:#"%#\n%#\n%#\n", message, placeHolder, placeHolder];
if (self = [self initWithTitle:title
message:_expandedMessage
delegate:delegate
cancelButtonTitle:cancelButtonTitle
otherButtonTitles:okButtonTitle, nil])
{
//Add switch.
self.alertSwitch = [[UISwitch alloc] init];
self.alertSwitch.on = YES;
[self addSubview:self.alertSwitch];
//Add label.
self.alertSwitchLabel = [[UILabel alloc] init];
self.alertSwitchLabel.text = switchMessage;
self.alertSwitchLabel.tag = kAlertSwitchLabelTag;
[self addSubview:self.alertSwitchLabel];
}
return self;
}
- (void)dealloc
{
self.alertSwitch = nil;
self.alertSwitchLabel = nil;
[super dealloc];
}
#pragma mark - Topography
- (void)layoutSubviews
{
NSLog(#"layoutSubviews to (%#)", NSStringFromCGRect(self.frame));
//Weak link to the message label.
UILabel *messageLabel;
//Enumerate subviews to find message label (the base of the topography).
for (UIView *eachSubview in self.subviews)
if ([[eachSubview class] isEqual:[UILabel class]])
{
UILabel *eachLabel = (UILabel*)eachSubview;
if (eachLabel.tag != kAlertSwitchLabelTag)
{
messageLabel = eachLabel;
NSLog(#"Each label frame (%#), saying '%#'", NSStringFromCGRect(eachLabel.frame), eachLabel.text);
}
}
//Center new content.
CGSize alertSwitchLabelSize = [self.alertSwitchLabel.text sizeWithFont:messageLabel.font];
float horizontalCentering = (messageLabel.frame.size.width - (alertSwitchLabelSize.width + ALERT_VIEW_LABEL_PADDING + self.alertSwitch.frame.size.width)) / 2;
//Switch goes to the bottom right.
float switchVerticalCentering = ((ALERT_VIEW_LINE_HEIGHT * 2 + 1) - self.alertSwitch.frame.size.height ) / 2;
CGRect alertSwitchFrame = CGRectMake(messageLabel.frame.origin.x + messageLabel.frame.size.width - self.alertSwitch.frame.size.width - horizontalCentering,
messageLabel.frame.origin.y + messageLabel.frame.size.height - self.alertSwitch.frame.size.height - switchVerticalCentering,
self.alertSwitch.frame.size.width,
self.alertSwitch.frame.size.height);
self.alertSwitch.frame = alertSwitchFrame;
//Label goes to the bottom left.
float switchLabelVerticalCentering = ((ALERT_VIEW_LINE_HEIGHT * 2 + 1) - ALERT_VIEW_LINE_HEIGHT ) / 2;
CGRect alertSwitchLabelFrame = CGRectMake(round( messageLabel.frame.origin.x + horizontalCentering ),
round( messageLabel.frame.origin.y + messageLabel.frame.size.height - ALERT_VIEW_LINE_HEIGHT - switchLabelVerticalCentering ),
messageLabel.frame.size.width - self.alertSwitch.frame.size.width,
ALERT_VIEW_LINE_HEIGHT); //self.alertSwitchLabel.frame.size.height);
self.alertSwitchLabel.frame = alertSwitchLabelFrame;
//Copy message label properties.
self.alertSwitchLabel.backgroundColor = [UIColor clearColor];
self.alertSwitchLabel.textColor = messageLabel.textColor;
self.alertSwitchLabel.font = messageLabel.font;
self.alertSwitchLabel.shadowColor = messageLabel.shadowColor;
self.alertSwitchLabel.shadowOffset = messageLabel.shadowOffset;
//Weaken.
self.alertSwitchLabel.alpha = ALERT_VIEW_LABEL_ALPHA;
[super layoutSubviews];
}
#end
The actual answer to the question is no -- Apple does not allow UIAlertView to be subclassed. From the UIAlertView docs:
Subclassing Notes
The UIAlertView class is intended to be used as-is and does not
support subclassing. The view hierarchy for this class is private and
must not be modified.
Found here:
Subclassing UIAlertView
No one but Apple can adequately answer this question, so the best thing is to put it to the test. I think the main question you have to ask yourself is: Have I violated any provisions in the Apple Developer Agreement? If not, then submit your app. If you are worried about rejection, think of another way in which this could be done, as a backup, and be prepared to submit that in case of a problem.
Not that you have asked, but I would also opine that this change to Apple's design is not very intuitive. Do you mean the switch to mean "also delete from moquus?" as you already have a big delete button there. If the switch is off, then what does the delete button delete?
So I have a subclass of UITableViewController that loads some data from the internet and uses MBProgressHUD during the loading process. I use the standard MBProgressHUD initialization.
HUD = [[MBProgressHUD alloc] initWithView:self.view];
[self.view addSubview:HUD];
HUD.delegate = self;
HUD.labelText = #"Loading";
[HUD show:YES];
This is the result:
.
Is there any way to resolve this issue, or should I just abandon MBProgressHUD?
Thanks!
My solution was pretty simple. Instead of using self's view, I used self's navigationController's view.
HUD = [[MBProgressHUD alloc] initWithView:self.navigationController.view];
[self.navigationController.view addSubview:HUD];
This should work for the OP because his picture shows he's using a UINavigationController. If you don't have a UINavigationController, you might add another view on top of your UITableView, and add the HUD to that. You'll have to write a little extra code to hide/show this extra view.
An unfortunate thing with this simple solution (not counting my idea adding another view mentioned above) means the user can't use the navigation controls while the HUD is showing. For my app, it's not a problem. But if you have a long running operation and the user might want to press Cancel, this will not be a good solution.
It's probably because self.view is a UITableView, which may dynamically add/remove subviews including the headers, which could end up on top of the HUD after you add it as a subview. You should either add the HUD directly to the window, or (for a little more work but perhaps a better result) you could implement a UIViewController subclass which has a plain view containing both the table view and the HUD view. That way you could put the HUD completely on top of the table view.
My solution was:
self.appDelegate = (kmAppDelegate *)[[UIApplication sharedApplication] delegate];
.
.
_progressHUD = [[MBProgressHUD alloc] initWithView:self.appDelegate.window];
.
[self.appDelegate.window addSubview:_progressHUD];
Works like a charm for all scenarios involving the UITableViewController. I hope this helps someone else. Happy Programming :)
Create a category on UITableView that will take your MBProgressHUD and bring it to the front, by doing so it will always appear "on top" and let the user use other controls in your app like a back button if the action is taking to long (for example)
#import "UITableView+MBProgressView.h"
#implementation UITableView (MBProgressView)
- (void)didAddSubview:(UIView *)subview{
for (UIView *view in self.subviews){
if([view isKindOfClass:[MBProgressHUD class]]){
[self bringSubviewToFront:view];
break;
}
}
}
#end
A simple fix would be to give the z-index of the HUD view a large value, ensuring it is placed in front of all the other subviews.
Check out this answer for information on how to edit a UIView's z-index: https://stackoverflow.com/a/4631895/1766720.
I've stepped into a similar problem a few minutes ago and was able to solve it after being pointed to the right direction in a different (and IMHO more elegant) way:
Add the following line at the beginning of your UITableViewController subclass implementation:
#synthesize tableView;
Add the following code to the beginning of your init method of your UITableViewController subclass, like initWithNibName:bundle: (the beginning of viewDidLoad might work as well, although I recommend an init method):
if (!tableView &&
[self.view isKindOfClass:[UITableView class]]) {
tableView = (UITableView *)self.view;
}
self.view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
self.tableView.frame = self.view.bounds;
self.tableView.contentInset = UIEdgeInsetsMake(0.0, 0.0, 0.0, 0.0);
[self.view addSubview:self.tableView];
Then you don't need to change your code you posted in your question any more. What the above code does is basically seperating the self.tableView from self.view (which was a reference to the same object as self.tableView before, but now is a UIView containing the table view as one might expect).
I've Just solved that issue manually , it has been 2 years since Chris Ballinger asked but maybe someone get used of what is going on here.
In UITableViewController i execute an HTTP method in viewDidLoad , which is running in background so the table view is loaded while the progress is shown causing that miss.
i added a false flag which is changed to yes in viewDidLoad, And in viewDidAppear something like that can solve that problem.
-(void) viewDidAppear:(BOOL)animated{
if (flag) {
[self requestSomeData];
}
flag = YES;
[super viewDidAppear:animated];
}
I had the same problem and decided to solve this by changing my UITableViewController to a plain UIViewController that has a UITableView as a subview (similar to what jtbandes proposed as an alternative approach in his accepted answer). The advantage of this solution is that the UI of the navigation controller isn't blocked, i.e. users can simply leave the ViewController in case they don't want to waiting any longer for your timely operation to finish.
You need to do the following changes:
Header file:
#interface YourViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
- (id)initWithStyle:(UITableViewStyle)style;
#end
Implementation file:
#interface YourViewController ()
#property (nonatomic, retain) UITableView *tableView;
#property (nonatomic, retain) MBProgressHUD *hud;
#end
#implementation YourViewController
#pragma mark -
#pragma mark Initialization & Memory Management
- (id)initWithStyle:(UITableViewStyle)style;
{
self = [super init];
if (self) {
// create and configure the table view
_tableView = [[UITableView alloc] initWithFrame:CGRectNull style:style];
_tableView.delegate = self;
_tableView.dataSource = self;
}
return self;
}
- (void)dealloc
{
self.tableView = nil;
self.hud = nil;
[super dealloc];
}
#pragma mark -
#pragma mark View lifecycle
- (void)loadView {
CGRect frame = [self boundsFittingAvailableScreenSpace];
self.view = [[[UIView alloc] initWithFrame:frame] autorelease];
// add UI elements
self.tableView.frame = self.view.bounds;
[self.view addSubview:self.tableView];
}
- (void)viewWillDisappear:(BOOL)animated
{
// optionally
[self cancelWhateverYouWereWaitingFor];
[self.hud hide:animated];
}
The method -(CGRect)boundsFittingAvailableScreenSpace is part of my UIViewController+FittingBounds category. You can find its implementation here: https://gist.github.com/Tafkadasoh/5206130.
In .h
#import "AppDelegate.h"
#interface ViewController : UITableViewController
{
MBProgressHUD *progressHUD;
ASAppDelegate *appDelegate;
}
In .m
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
appDelegate = (ASAppDelegate *) [UIApplication sharedApplication].delegate;
progressHUD = [MBProgressHUD showHUDAddedTo:appDelegate.window animated:YES];
progressHUD.labelText = #"Syncing To Sever";
[appDelegate.window addSubview:progressHUD];
This should work.
[MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];
And to remove you can try
[MBProgressHUD hideHUDForView:self.navigationController.view animated:YES];
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.
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];
}
}
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.
this code is work for me
[splitViewController setValue:[NSNumber numberWithFloat:200.0] forKey:#"_masterColumnWidth"];
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.
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.
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
The storyboard way would be this one, mentioned by #Tim:
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.
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.
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!
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;
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
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;
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.
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;
}
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)
// 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];
}
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)