I am trying to build a custom split view controller for iPad using 2 container views that embed UINavigationControllers...
I managed to do all that but I am having problems with setting up constraints so that everything resizes nicely.
This is what I have so far:
I wont to be able to resize master view (left container) in a way that detail view (right container) resizes itself also to consume the space that master view left while resizing it self. So I would like to end up like this:
among other tries I tried adding these constraints:
master container: top to parent, left to parent, bottom to tabbar, height, placeholder width
detail container: top to parent, left to master container, bottom to tabbar, right to parent, height, placeholder width
When I resize master container from code, the detail controller always stays at its original place.
I tried 100 scenarios and nothing worked yet.
I am doing something fundamentally wrong with auto layout but just cant figure out what..
This is the resize code:
-(void)ShowMaster:(BOOL)bShow animated:(BOOL)bAnimated
{
CGRect frame = masterViewContainer.frame;
if(bShow){
frame.size.width = 280;
}
else{
frame.size.width = 50;
}
masterViewContainer.frame = frame;
}
Any kind of help would be much appreciated.
EDIT:
OK, I made some progress, replaced my resize code with this one:
-(void)ShowMaster:(BOOL)bShow animated:(BOOL)bAnimated
{
[UIView beginAnimations:#"" context:nil];
if(bShow){
[masterViewContainer addConstraint:[NSLayoutConstraint constraintWithItem:masterViewContainer
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:masterViewContainer
attribute:NSLayoutAttributeWidth
multiplier:0.5
constant:280]];
}
else{
[masterViewContainer addConstraint:[NSLayoutConstraint constraintWithItem:masterViewContainer
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:masterViewContainer
attribute:NSLayoutAttributeWidth
multiplier:0.5
constant:50]];
}
[UIView commitAnimations];
}
Now detail resizes, but when I try to expand master, nothing happens...
try this:
-(void)viewDidLayoutSubviews {
//your code
}
I resolved it. In my viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
if(masterWidthConstraint == nil){
masterWidthConstraint = [NSLayoutConstraint constraintWithItem:masterViewContainer
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:masterViewContainer
attribute:NSLayoutAttributeWidth
multiplier:0.0
constant:280];
[masterViewContainer addConstraint:masterWidthConstraint];
}
}
In my ShowMaster method i just update that constraint and animate like this:
-(void)ShowMaster:(BOOL)bShow animated:(BOOL)bAnimated
{
[UIView beginAnimations:#"" context:nil];
if(bShow){
masterWidthConstraint.constant = 280;
}
else{
masterWidthConstraint.constant = 50;
}
[self.view setNeedsUpdateConstraints];
[UIView animateWithDuration:0.9 animations:^{
[self.view layoutIfNeeded];
} completion:^(BOOL finished){}];
}
And in my IB i set placeholder constraint form master view width... And it works. Only thing that I couldn't do is make a IBOutlet to IB width constraint, and couldn't figure out why, but it works like this just fine so...
Related
I'm attempting to create a custom view that appears exactly like the keyboard, but I can't figure out how to use frame manipulation and/or programmatic auto layout to solve my problem.
Some context: I have a messaging app style view controller with a textview and button docked at the bottom of the screen. All views are wrapped into a nice single content view with autolayout set such that when the keyboard appears, it pushes the entire view up, and when it disappears it pushes the entire view back down. This is the behavior I'm trying to reproduce.
I messed around with trying to manually resize frames similar to my keyboard code but ended up throwing that away in favor of a auto-layout based solution. Here's what I have so far:
StickersCollectionViewController *stickerController = [self.storyboard instantiateViewControllerWithIdentifier:#"StickersCollectionViewController"];
[self addChildViewController:stickerController];
[self.view addSubview:stickerController.view];
[stickerController didMoveToParentViewController:self];
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:stickerController.view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:self.view.bounds.size.height];
[self.view addConstraint:constraint];
NSLayoutConstraint *width = [NSLayoutConstraint constraintWithItem:stickerController.view attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual toItem:self.view
attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0.0];
NSLayoutConstraint *height = [NSLayoutConstraint constraintWithItem:stickerController.view
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:240.0];
[self.view addConstraint:width];
[self.view addConstraint:height];
double delayInSeconds = 0.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
constraint.constant = 240.0;
[UIView animateWithDuration:0.3
animations:^{
[self.view layoutIfNeeded];
}];
});
So far, this looks great: the view is instantiated and added just off screen, then animates into view. However, I'd also like my superview (mentioned above) to also animate with this view. This is the piece I need help with.
Can anyone offer help in this direction? Or offer a suggestion as to a different route I could go? Thanks.
Try this instead, the difference is using "[self.view layoutIfNeeded];" both before the animation and after the animation, and then placing this "constraint.constant = 240.0;" in the animation.
double delayInSeconds = 0.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self.view layoutIfNeeded];
[UIView animateWithDuration:0.3
animations:^{
constraint.constant = 240.0;
[self.view layoutIfNeeded];
}];
});
So, try this as well:
this:
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:stickerController.view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:self.view.bounds.size.height];
shoud probably be this:
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:stickerController.view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];
because you are constraining to the top of the view and not constraining to the top of the view + self.view.bounds.size.height, not sure if this helps, but this is one idea.
A second possible solution is this, wrap up all UI elements into a UIView and set this UIView as the view of stickerController.view, this isn't so straight forward because technically you should assign this view as the view of stickerController.view in a load view method in the stickerController impelemntation file and then type cast the stickerController.view as this for example
Typcasting inside implementation file of stickerController
- (void)loadView
{
[self setView:[ContainerViewThatHasSubViews new]];
}
- (ContainerViewThatHasSubViews*)contentView
{
return (id)[self view];
}
then, create a subclass of ContainerViewThatHasSubViews like so
ContainerViewThatHasSubViews.h
#interface ContainerViewThatHasSubViews : UIView
#property (nonatomic) UIView *subContainerOfContainerViewThatHasSubViews;
#property (nonatomic) NSLayoutConstraint *tester;
#end
ContainerViewThatHasSubViews.m
#interface ContainerViewThatHasSubViews ()
#end
#implementation ContainerViewThatHasSubViews {
}
self = [super initWithFrame:frame];
if (self) {
_subContainerOfContainerViewThatHasSubViews = [UIView new];
[_subContainerOfContainerViewThatHasSubViews setTranslatesAutoresizingMaskIntoConstraints:false];
[self addSubview:subContainerOfContainerViewThatHasSubViews];
/// PSUEDO CODE HERE NOW
ALL OTHER UI ELEMENTS ARE ADDED TO THE UIVIEW "subContainerOfContainerViewThatHasSubViews"
like this *** [subContainerOfContainerViewThatHasSubViews addSubView:**another ui element***];***
etc. etc. etc.
then use layout constraints like this:
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:stickerController.view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:self.view.bounds.size.height];
[subContainerOfContainerViewThatHasSubViews.view addConstraint:constraint];
etc. etc., so the layout constraints are added to "subContainerOfContainerViewThatHasSubViews"
add separate constraints to this UIView for the items inside this view, and you can then animate these cosntraitns as well by declaring the constraints in your header file as propertyies like i did with the _tester constraint, you can animate these when you press a button in your UIViewController impelmentation file by addding a gesture recognizer or whatever you have to show the keyboard
then end with this:
NSDictionary* views = NSDictionaryOfVariableBindings(subContainerOfContainerViewThatHasSubViews);
NSDictionary* metrics = #{#"sp" : #(heightResizer(10)), #"sh" : #40}; //include sizing metrics here
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_subContainerOfContainerViewThatHasSubViews]|" options:0 metrics:metrics views:views]];
and this:
_tester =[NSLayoutConstraint constraintWithItem:_subContainerOfContainerViewThatHasSubViews attribute:NSLayoutAttributeCenterTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterTop multiplier:1.0f constant:0.0f];
[self addConstraint:_tester];
}
return self;
}
#end
Gist:
https://gist.github.com/anonymous/c601481d24ad1b98b219
https://gist.github.com/anonymous/b22b68d4bf8d7fa51d66
https://gist.github.com/anonymous/a9aaf922e0f5383256b6
https://gist.github.com/anonymous/fc6655ea8200cda9c0dd
There's a lot to this one, but it's just how programatic views work. I've never use storyboard because I feel like I have more control doing everything programatically, if you can handle how much code you'll need to write to make this happen then you are good to go, I know this because I'm building an app just like you that does the same thing that you want yours to do, and this works for me because I'm digging deep into the views. This is complete control, just make sure you make your other NSLayoutContraints as properities of the subclassed UIView, and then you can place methods in your subclassed UIView's header file that point to he implementation file of the UIView subclass and these methods can be the animation methods that you can call from your view controller like this:
[[self contentView] CALLYOURFUNCTION];
to access the other layout constraints and ui elements from the subclassed UIView in your View controller do this to call them:
[[self contentView] tester]; //this is the constraint from9 the header file of tbhe custom UIView
to set the constant in your UIViewController do this:
[[[self contentView] tester] setConstant:A_NUMBER];
The point is this, you are encapsulating all your views into a UIView subclass rather than accessing the view of your view controller itself and trying to animate it's view. I've not ever seen someone try to animate the "view" property of a UIViewController itself, I've only seen the animation of a UIView that is a subview of the UIViewController's view. The Point also is that you are forcing this UIView to be the view of the UIViewControllers view and then you are using another UIView within the subClassed UIView to animate the entire Contents of the view, so it's like this:
-UIViewController's view
Sub Class of UIView
UIView <=== this is the view that contains ALL THE OTHER UIElements of the View controller
Add a constraint to the UIView that contains all other UI elements, this contraint is the tester constraint I added to the header of the custom Sub class UIView.
You can then add additional constraints to the subViews of the the UIView that I've mentioned which contains all other UI elements of the UIViewController
When you add constraints on these sub items, you can animate them as well. This method is advanced so ask questions if you have them, my answer is a little messy.
daddy warbucks answer did indeed get me close to the solution I was looking for. For those of you who have built most of your UI skeleton with the storyboard, I ended up adding a container view to the bottom of my view controller and modifying the center values of the main content view and container view on button press.
Layout content view as you normally would. Set Leading, Trailing, and Top constraints to the content view, and a Bottom constraint to the container view (described in step 2).
Create a container view and dock it below the content view. Set equal width to the content view, Top constraint to the Bottom of the content view, and a height of 240 (or whatever you wish). Note: the view will be positioned OFF the view controller. If you're slightly OCD like me, this will bother you for a little bit until you realize that it's k.
Here's what I used to move the container view into place and have the content view follow with it. Note that I probably will clean this up more, but this is the general idea:
if (_isStickersViewOpen) {
self.tableView.contentInset = UIEdgeInsetsZero;
self.tableView.scrollIndicatorInsets = UIEdgeInsetsZero;
[UIView animateWithDuration:0.3
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
self.contentView.center = CGPointMake(self.scrollView.center.x, self.scrollView.center.y);
self.containerView.center = CGPointMake(self.scrollView.center.x, self.scrollView.bounds.size.height + self.containerView.bounds.size.height / 2);
}
completion:^(BOOL finished){
_isStickersViewOpen = NO;
}];
} else {
UIEdgeInsets contentInsets = UIEdgeInsetsMake(self.containerView.frame.size.height, 0.0, 0.0, 0.0);
self.tableView.contentInset = contentInsets;
self.tableView.scrollIndicatorInsets = contentInsets;
[UIView animateWithDuration:0.3
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
self.contentView.center = CGPointMake(self.scrollView.center.x, self.scrollView.bounds.size.height / 2 - self.containerView.bounds.size.height);
self.containerView.center = CGPointMake(self.scrollView.center.x, self.scrollView.bounds.size.height - self.containerView.bounds.size.height / 2);
}
completion:^(BOOL finished){
_isStickersViewOpen = YES;
}];
}
I'm not claiming this is the most effective or clean solution available, but it works for my purpose.
I am new to Auto layout constraints. I have 2 views(topView and paintView) on my main view, along with a button on the top right corner of the main view. On loading the view, the topView occupies the whole main view(excluding the button). On click of the button, I want the topView to occupy 70% of the main view and the paintView to occupy the rest(excluding the button).
I have set up the the X, Y and top constraints for the topView using storyboard. The paintView and the corresponding constraints have been set up programmatically.
The code I have now is this:
-(void)setupPaintView
{
UIView *pPaintView = [UIView new];
[pPaintView setBackgroundColor:[UIColor yellowColor]];
pPaintView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:pPaintView];
self.paintView = pPaintView;
[self addConstraintsToView];
//[self setTopViewFrame];
}
-(void)addConstraintsToView
{
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.paintView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.topView attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0.0]];
[self.view addConstraint:[NSLayoutConstraint
constraintWithItem:self.paintView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.topView
attribute:NSLayoutAttributeWidth
multiplier:1.0
constant:0.0]];
[self.view addConstraint:[NSLayoutConstraint
constraintWithItem:self.topView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.paintView
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0.0]];
NSLayoutConstraint *pHeightConstraintTopView = [NSLayoutConstraint
constraintWithItem:self.topView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeHeight
multiplier:1.0
constant:0.0];
self.heightconstraintTopView = pHeightConstraintTopView;
[self.view addConstraint:pHeightConstraintTopView];
NSLayoutConstraint *pHeightConstraintPaintView = [NSLayoutConstraint
constraintWithItem:self.paintView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeHeight
multiplier:0.0
constant:0.0];
self.heightconstraintPaintView = pHeightConstraintPaintView;
[self.view addConstraint:pHeightConstraintPaintView];
}
On button click the following method gets called:
-(IBAction)detailBtnClick:(id)sender
{
if(self.heightconstraintPaintView.constant == 0)
{
self.heightconstraintTopView.constant = 0.7*self.view.frame.size.height;
self.heightconstraintPaintView.constant = 0.3*self.view.frame.size.height;
[self.view setNeedsUpdateConstraints];
}
else
{
self.heightconstraintTopView.constant = self.view.frame.size.height;
self.heightconstraintPaintView.constant = 0;
[self.view setNeedsUpdateConstraints];
}
}
When the view loads, the topView acquires the main view's height, which is desired here. But when I click on the button, the topView remains at 100% i.e. it does not resize and neither does the paintView. I am modifying the constant property of the topView and the paintView constraints, but I am not sure that is the correct way to go about it. The constraint here is that the views have to be laid out using Autolayout constraints only. How can I get the views to resize at the click of the button?
Any help is welcome.
Thanks to timothykc and others, I have successfully navigated the problem stated above. But I am facing another issue now.When I change the orientation of the simulator to landscape, the paintView remains almost hidden. Following is the code (toggle is a boolean value that decides whether to stretch/shrink the views):
-(IBAction)detailBtnClick:(id)sender
{
if(self.toggle == FALSE)
{
self.topViewHeightConstraint.constant = 0.7*self.bounds.frame.size.height;
self.heightconstraintPaintView.constant = 0.3*self.bounds.frame.size.height;
//[self.view layoutIfNeeded];
}
else
{
self.topViewHeightConstraint.constant = self.view.bounds.size.height;
self.heightconstraintPaintView.constant = 0;
//[self.view layoutIfNeeded];
}
self.toggle = !self.toggle;
}
The topViewHeightConstraint has been added as a property as indicated by timothykc. This is working properly for the portrait orientation, but is not working properly for landscape, as the height of the topView does not change as desired(70%), meaning that the ratios are not getting handled properly.
I'm going to provide a storyboard driven solution that should help you with other autolayout problems down the road.
My solution to your specific problem, you've got two views (1 and 2 in diagram below):
For view 1, pin the view to the left, top, and right of the superview. Then set a height constant. (e.g. 568, the full height of an iphone 5s)
For view 2, pin it to the left, bottom, and right of the superview. Then pin it to the bottom of view 1.
Open up the assistant editor view, and here's the key trick--turn the height constraint on view 1 into a nslayoutconstraint property on your VC. You do this by locating the constraint, and then control-dragging onto the VC. (e.g.`
#property (strong, nonatomic) IBOutlet NSLayoutConstraint *viewHeight;`
Now you can manipulate this property with an action linked to your button, such as
- (IBAction)scale:(id)sender {
self.viewHeight.constant = 397.6; //%70 of 568....
}
In my example, I change the nslayoutconstraint.CONSTANT manually to an arbitrary value.
To understand what's happening, you need to know that autolayout is a means for determining the (x coord,y coord,width, height) of any layout object. Warnings occur when xcode cannot ascertain all 4 values...
In View 1, we give a constraint for Height. X,Y, and Width are extrapolated from the distance to the superview. (if something is 0 from the left and right, then the width fills the whole screen...; if 0 from top and left, then coords must be (0,0))
In view 2, X must be 0 since distance from left is 0. width whole screen... Height and Y are extrapolated based on the height of View 1!
So when we mess with height constraint in View 1, it effects the height and Y coord of View 2!
To get constraints to update on a view you would need to call [self.view layoutIfNeeded]; instead of [self.view setNeedsUpdateConstraints]; after setting the new constant on whichever constraint(s) you would like to update.
Actually this is more of an comment about my methods, but I decided to post it as an answer because firstly, this has solved my problem and secondly, it involves some snippets of code which is hard to read in the comments section. Regarding the orientation problem mentioned in the edit, I came up with a workaround to accommodate the view reszing requirements with respect to the toggle button and with respect to orientation change. The three methods used for this purpose are:
The following method is called on the button click event.
-(IBAction)detailBtnClick:(id)sender
{
[self updateViewConstraints:self.toggle];
self.toggle = !self.toggle;
}
The following method updates the constraints.
-(void)updateViewConstraints :(BOOL)toggleValue
{
if(toggleValue == FALSE)
{
self.topViewHeightConstraint.constant = 0.7*self.view.bounds.size.height;
self.heightconstraintPaintView.constant = 0.3*self.view.bounds.size.height;
}
else
{
self.topViewHeightConstraint.constant = self.view.bounds.size.height;
self.heightconstraintPaintView.constant = 0;
}
}
The following method calls the method above to update constraints in case of orientation change:
-(void)viewWillLayoutSubviews
{
[self updateViewConstraints:!self.toggle];
}
In my app I need to have a UITableView in half of one of the views (from half view to the bottom). The problem is when I rotate the device, the tableview changes its position to always respect the height constraint.
UITableView at portrait
UITableView at landscape (when the problem occurs)
The main idea is to shrink the tableview to always fit half of the view.
I assume you are setting the frame of your tableView in viewDidLoad or init; instead, use viewDidLayoutSubviews in your view controller to layout your views. This is called every time the view controller's bounds changes, even after a rotate:
-(void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
self.tableView.frame = CGRectMake(0, CGRectGetMidY(self.view.bounds), self.view.bounds.size.width, self.bounds.size.height / 2);
}
self.tableView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addConstraint:[NSLayoutConstraint
constraintWithItem:self.tableView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeHeight
multiplier:0.5
constant:0]];
In my app I want to achieve this layout:
So parent view contains two sub views. First one ends exactly in a middle (height / 2) and second starts in a middle of parent view. I have found out that it is impossible to do that in the IB with constraints. So I used this code in viewDidLoad method:
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:firstView
attribute:NSLayoutAttributeHeight
relatedBy:0
toItem:self.view
attribute:NSLayoutAttributeHeight
multiplier:0.5
constant:0];
[self.view addConstraint:constraint];
Now it works but only if the app runs on the iPhone. Because size of the view is like iPhone screen. If this app runs on the iPad, there is a problem because screen has different size so this parent view is longer. And constraint (code above) still takes 0.5 * size of the views size from the IB and not size from the iPad size of the view. Item toItem:self.view still takes size from the IB.
Result is that this view has a same size in the iPad as in the iPhone. In the iPad there is a large blank space and then there is a view with iPhone size.
Can you tell what I have to do to make it universal for various screen sizes? Thank you very much
This is possible using constraints, but it is made a bit fiddly by IBs rather annoying and inflexible constraint manager. Here is how I managed it:
In IB, set the two views with the correct frames
Add an equal height constraint between the two views
Reduce the priority of any default height constraints on either of the views. Unfortunately IB does not let you remove these entirely, but setting them to anything less than 1000 will make sure they are ignored.
In the view controllers viewDidLoad method, add the constraint you already tried.
eg
- (void)viewDidLoad
{
[super viewDidLoad];
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:self.topView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeHeight
multiplier:0.5
constant:0];
[self.view addConstraint:constraint];
}
Thats it. Screengrabs of the IB constraints are shown below:
Try this code . It will set constraint value dynamically
In your .h file , implement this lines.
#define IS_IPAD (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
#define IS_IPHONE_5 ( fabs( ( double )[ [ UIScreen mainScreen ] bounds ].size.height - ( double )568 ) < DBL_EPSILON )
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *TopsubviewheightConstraint;
Now create this constraint's as per given screen shot
connect TopsubviewheightConstraint height constraint from screen
implement this code in .m file
if (IS_IPHONE_5)
_TopSuperViewConstraint.constant = 275;
else if(IS_IPAD)
_TopSuperViewConstraint.constant = 502;
else
_TopSuperViewConstraint.constant = 230;
I hope it will help you.
you have 2 options.
create a second IB file for iPad
do everything by programm and use [[UIScreen mainScreen] bound]; instead of getting the sizes of parent ;)
I would do it without the constraints at all and set as follow:
// self.view is my container view
CGRect frame = [[UIScreen mainScreen] bound];
frame.size.height /= 2;
// upper View
upperView.frame = frame;
// lower View
frame.origin.y = frame.size.height;
// or alternatively
//frame.origin.y = CGRectGetMaxY(frame);
lowerView.frame = frame;
here you don't need any device specific options, everything is dynamic, bound to the size of your device's screen ;)
OK so I just figured out how to do this. Simply put the code into viewDidLayoutSubviews method and not to viewDidLoad. The solution I found in the topic Unable to set frame correctly before viewDidAppear.
Here is my code:
[subView1 setFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height / 2)];
[subView2 setFrame:CGRectMake(0, self.view.frame.size.height / 2, self.view.frame.size.width, self.view.frame.size.height / 2)];
Thanks to all for effort!
Thanks to Tark's answer, I managed to to this using constraints as well:
Add Vertical Space constraint for TobView to Top Layout Guide (Using StoryBoard)
Add Vertical Space constraint for BottomView to Bottom Layout Guide (Using StoryBoard)
Add two height constraints for each view in ViewDidLoad
Code:
NSLayoutConstraint *constraint;
constraint = [NSLayoutConstraint constraintWithItem:_viewTop
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeHeight
multiplier:0.5
constant:0];
[self.view addConstraint:constraint];
constraint = [NSLayoutConstraint constraintWithItem:_viewBottom
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeHeight
multiplier:0.5
constant:0];
[self.view addConstraint:constraint];
You can do this with constrains (no code required!)
1.- First create two UIview and manually set it's height to half of the size of the current device, positioning one over the other, just like this:
2.- Next you must set the constraints for each one of them like this (this will allow to the container fill the whole screen, one over the another):
Top container
Bottom container
3.- Finally you must select both containers and add a new constrain that specify that they will have in the same height
(remember to clid "Add X Constrains" for each step)
now it should be ready to put the label inside each container, and you will ready
I'm having an issue with auto layout and constraints and could use some help.
I am running this application on an iPad. I have a window that contains two views, a UIWebView and an MKMapView. Both of those views are set up in IB, and Auto Layout is turned on. The UIWebView is positioned at the top of the window and the MKMapView is at the bottom. Each view takes up almost half of the window. The UIWebView has the following constraints set up in IB: NSLayoutAttributeTop to Superview equal to 0, Leading Edge to Superview equal to 0, Trailing Edge to Superview equal to 0, and NSLayoutAttributeBottom to Superview equal to 480. The MKMapView has the following constraints set up in IB: NSLayoutAttributeTop to Superview equal to 480, Leading Edge to Superview equal to 0, Trailing Edge to Superview equal to 0, and NSLayoutAttributeBottom to Superview equal to 0.
When the window is loaded, the MKMapView is actually removed, since I want the UIWebView to take up the entire screen, because there is no data to display in the map view. This is done in my updateDetailViews function:
- (void)updateDetailViews
{
displayHeight = self.maximumUsableFrame.size.height;
viewDistance=displayHeight/2+centerMapButton.frame.size.height/2+16;
[detailMapView setTranslatesAutoresizingMaskIntoConstraints:NO];
[directoryWebView setTranslatesAutoresizingMaskIntoConstraints:NO];
if (mapViewVisible==true) {
webViewDistFromBottomDefault=viewDistance;
webViewDistFromBottom.constant=viewDistance;
mapViewDistFromTopDefault=viewDistance;
mapViewDistFromTop.constant=viewDistance;
}
else {
[detailMapView removeFromSuperview];
webViewDistFromBottomDefault=0;
webViewDistFromBottom.constant=0;
mapViewDistFromTopDefault=viewDistance;
mapViewDistFromTop.constant=viewDistance;
}
[detailMapView setNeedsUpdateConstraints];
[UIView animateWithDuration:0.25f animations:^{
[self.detailMapView layoutIfNeeded];
}];
}
After the MKMapView is removed, the NSLayoutAttributeBottom attribute of UIWebview is set to 0, and it fills the entire screen. Once there is actual data to show in the map, the MKMapView is then added, and the UIWebView repositioned, along with the necessary constraints, in my displayMapView function:
- (void)displayMapView
{
double dblLatitude;
double dblLongitude;
[detailMapView setTranslatesAutoresizingMaskIntoConstraints:NO];
if ([self isMapViewDisplayed]==FALSE) {
[detailView addSubview:detailMapView];
NSLayoutConstraint *myConstraint =[NSLayoutConstraint
constraintWithItem:detailMapView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:detailView
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:mapViewDistFromTopDefault];
[detailView addConstraint:myConstraint];
//mapViewDistFromTop.constant = mapViewDistFromTopDefault;
myConstraint =[NSLayoutConstraint
constraintWithItem:detailMapView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:detailView
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0];
[detailView addConstraint:myConstraint];
myConstraint =[NSLayoutConstraint
constraintWithItem:detailMapView
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:detailView
attribute:NSLayoutAttributeLeading
multiplier:1.0
constant:0];
[detailView addConstraint:myConstraint];
myConstraint =[NSLayoutConstraint
constraintWithItem:detailMapView
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:detailView
attribute:NSLayoutAttributeTrailing
multiplier:1.0
constant:0];
[detailView addConstraint:myConstraint];
[detailMapView setNeedsUpdateConstraints];
}
[UIView animateWithDuration:.5
animations:^{
webViewDistFromBottom.constant=webViewDistFromBottomDefault;
mapViewDistFromTop.constant=mapViewDistFromTopDefault;
[self.directoryWebView layoutIfNeeded];
[self.detailMapView layoutIfNeeded];
}];
[self updateDetailViews];
...
if ((dblLatitude != 0) && (dblLongitude != 0)) {
zoomLocation.latitude = dblLatitude;
zoomLocation.longitude = dblLongitude;
MKCoordinateRegion viewRegion = MKCoordinateRegionMakeWithDistance(zoomLocation, METERS_PER_MILE, METERS_PER_MILE);
MKCoordinateRegion adjustedRegion = [detailMapView regionThatFits:viewRegion];
[detailMapView setRegion:adjustedRegion animated:YES];
}
CLLocationCoordinate2D coordinate;
coordinate.latitude = dblLatitude;
coordinate.longitude = dblLongitude;
...
[detailMapView addAnnotation:annotation];
}
All of this works like I intend. The problem occurs when the device is rotated. If I start with the iPad in portrait mode, the webViewDistFromBottom and mapViewDistFromTop constraints are set to 490, due to the updateDetailViews function above which has the following calculations:
displayHeight = self.maximumUsableFrame.size.height;
viewDistance=displayHeight/2+centerMapButton.frame.size.height/2+16;
If the iPad is rotated to landscape, the willAnimateRotationToInterfaceOrientation function is called, which then calls updateDetailViews, which sets viewDistance to 367 (and correspondingly webViewDistFromBottom.constant and mapViewDistFromTop.constant). The UIWebView on top looks as it should, however, the MKMapView on bottom does not. The mapViewDistFromTop constraint is set to 367 (if I output the value to the log), however it appears that it is still set to 490. My updateDetailViews function calls [self.view layoutIfNeeded] (and I have also tried [detailMapView layoutIfNeeded], [detailMapView setNeedsLayout]), but that view does not show up correctly. The distance from the top is too large. If I rotate the iPad back to portrait, it looks fine.
I also have the same problem if the iPad is started in landscape mode, then rotated to portrait. In landscape mode, the mapViewDistFromTop and webViewDistFromBottom values are 367, and are set to 490 once rotated to portrait. However, the MKMapView on the bottom looks as those the distance from the top is still 367, covering too much of the display.
Any idea what I'm doing wrong? Thanks in advance for any assistance!!!
If I understand the question correctly, when in portrait you want a map view, 480 points tall, at the bottom, and if in landscape, you want the web view to take up the whole screen. An alternative approach would be to just modify the height of the map view (480 in portrait, 0 in landscape). Don't remove the map view, just set its height to 0. And let the existing constraints take care of everything else. Then there are no adding of modifying of constraints, views, etc. All you need to do is adjust one constant on rotation. Does that do the job?
To illustrate this, in this scenario I'm suggesting you set up your constraints (I'll only focus on the vertical constraints) so that they are equivalent to
V:|[webView][mapView(480)]|
(I'm not suggesting you use VFL to specify the constraints, but it's just the most concise way to articulate the series of constraints I used.) Note, make sure you don't have any extraneous constraints floating around (e.g. web view bottom constraint to super view, etc.). I'm proposing, in the vertical dimension, just these constraints (web view top to super view, web view bottom to map view top, map view height, and map view bottom to super view).
Then, define and link an outlet for the height constraint of the mapView:
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *mapViewHeightConstraint;
Finally, on rotation, just change the constant for that constraint:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
if (UIInterfaceOrientationIsPortrait(toInterfaceOrientation))
self.mapViewHeightConstraint.constant = 480.0;
else
self.mapViewHeightConstraint.constant = 0.0;
}
If I understand the UI you're shooting for, I think that's all you need, eliminating all of that other code in your question. I just tested it and it seems to work fine.