I think I have a fundamental issue with these new constraints.
Imagine you put 5 labels all going from top to bottom in a UIView. The top label is equally spaced between the top of the view and the next label underneath it. Then each label is also equally spaced.
What I would like to happen is that when the UIView height is made less or more the space between each label changes equally?
What should I be aiming for. Should I make the 3rd label vertically centred in the view and then add constraints to the other labels from the centre or should 1 and 2 be constrained to the top of the view, 3 centre and 4 and 5 to the bottom?
Thanks in advance for all your suggestions.
Edit:
After the below answer was added I have played around with the constraints in interface builder and whilst I am not 100% sure I have answered it I think I have it working.
Between all labels I have a certain distance. But I also have a certain minimum distance like following apples HIG I think it is 15 between labels. So mine are at 30 and I set a constraint of greater than or equal to 15. Now iOS does all the moving around from 30 to 15 for me when the superview changes.
I originally did this with buttons, and the only way I could make this work so that it would automatically adjust to the view frame size (on rotation) was to add labels (with no text, so they're invisible) between each button, and between the containing view and the first and last button. The buttons all had an intrinsic size and the labels did not, so they were free to change their heights to fill the space. I modified the code to use all labels, with the "b" labels being the ones with text.
-(void)viewDidLoad {
[super viewDidLoad];
NSMutableDictionary *viewsDict = [NSMutableDictionary dictionary];
for (int i=1; i<5; i++) {
UILabel *b = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 150, 44)];
b.text = #"This is my label";
[b setTranslatesAutoresizingMaskIntoConstraints:NO];
[viewsDict setObject:b forKey:[NSString stringWithFormat:#"b%d",i]];
}
for (int i=1; i<6; i++) { // these are the spacer labels
UILabel *l = [[UILabel alloc ]init];
[l setTranslatesAutoresizingMaskIntoConstraints:NO];
[viewsDict setObject:l forKey:[NSString stringWithFormat:#"l%d",i]];
}
for (id obj in viewsDict.allKeys)
[self.view addSubview:viewsDict[obj]];
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[l1][b1][l2(==l1)][b2][l3(==l1)][b3][l4(==l1)][b4][l5(==l1)]|"
options:NSLayoutFormatAlignAllLeading
metrics:nil
views:viewsDict];
NSArray *constraints2 = [NSLayoutConstraint constraintsWithVisualFormat:#"|-[b1]"
options:0
metrics:nil
views:viewsDict];
[self.view addConstraints:constraints];
[self.view addConstraints:constraints2];
}
This will produce evenly spaced labels with the standard space to the left edge of the view.
Related
i have to add some constraints programmatically in a customized UITableViewCell, i have a mainView (red) it will be a container (a subView of cell.contentView), an image and 2 other subviews of the mainView.
this is what i want to have (blue and yellow views don't have the same height, but this is an other question):
using the code bellow (i'm not adding the imageView yet), i have this:
as you can see the 2 subviews have the same size, and when i scroll, sometime the blue view is displayed and sometime it's the yellow one.
code :
self.mainView = [[UIView alloc]init];
[self.mainView setBackgroundColor:[UIColor redColor]];
[self.mainView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.contentView addSubview:self.mainView];
UIView *blueView = [[UIView alloc]init];
[blueView setTranslatesAutoresizingMaskIntoConstraints:NO];
[blueView setBackgroundColor:[UIColor blueColor]];
[self.mainView addSubview:blueView];
UIView *yelloView = [[UIView alloc]init];
[yelloView setBackgroundColor:[UIColor yellowColor]];
[yelloView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.mainView addSubview:yelloView];
NSDictionary *views = NSDictionaryOfVariableBindings(blueView,yelloView,mainView);
NSMutableArray *results = [NSMutableArray array];
NSString *mainContentView_CVF_H = #"H:|-[blueView]-|";
NSString *mainContentView_CVF_V = #"V:|-[blueView]";
NSArray *mainContentViewConstaints_H = [NSLayoutConstraint constraintsWithVisualFormat:mainContentView_CVF_H options:0 metrics:nil views:views];
NSArray *mainContentViewConstaints_V = [NSLayoutConstraint constraintsWithVisualFormat:mainContentView_CVF_V options:0 metrics:nil views:views];
[results addObjectsFromArray:mainContentViewConstaints_H];
[results addObjectsFromArray:mainContentViewConstaints_V];
NSString *delivelyView_CVF_H = #"H:|-[yelloView]-|";
NSString *delivelyView_CVF_V = #"V:[blueView][yelloView]-|";
NSArray *delivelyViewConstaints_H = [NSLayoutConstraint constraintsWithVisualFormat:delivelyView_CVF_H options:0 metrics:nil views:views];
NSArray *delivelyViewConstaints_V = [NSLayoutConstraint constraintsWithVisualFormat:delivelyView_CVF_V options:0 metrics:nil views:views];
[results addObjectsFromArray:delivelyViewConstaints_H];
[results addObjectsFromArray:delivelyViewConstaints_V];
[self.mainView addConstraints:results];
NSString *mainView_CVF_H = #"H:|-[mainView]-|";
NSString *mainView_CVF_V = #"V:|-[mainView]-|";
results = [NSMutableArray array];
NSArray *mainViewConstaints_H = [NSLayoutConstraint constraintsWithVisualFormat:mainView_CVF_H options:0 metrics:nil views:views];
NSArray *mainViewConstaints_V = [NSLayoutConstraint constraintsWithVisualFormat:mainView_CVF_V options:0 metrics:nil views:views];
[results addObjectsFromArray:mainViewConstaints_H];
[results addObjectsFromArray:mainViewConstaints_V];
[self.contentView addConstraints:results];
i replace the subviews(blue and yellow) with 2 text filed without any modification, and they are displayed as expected.
2 questions:
can you help :) ?
the order of adding constraints is important ?
A couple of thoughts:
No, the order of constraints is not important.
As an aside, you could combine two of your vertical constraint VLF into one:
#"V:|-[blueView][yellowView]-|"
The reason for the strange heights of blueView and yellowView is that your constraints are ambiguous. You define that blue and yellow, together, should span the height of their shared superview, but you leave it to iOS to decide precisely how tall each will will be individually. This ambiguity can be confirmed by running your app in the debugger, pausing the app once the app has finished presenting this particular set of views, and then at the (lldb) prompt you can enter:
po [[UIWindow keyWindow] _autolayoutTrace]
You'll probably see results that say something like this simplified example:
$0 = 0x075667b0
*<UIWindow:0x718a760>
| *<UIView:0x718d090>
| | *<UIView:0x7562490>
| | | *<UIView:0x75627d0> - AMBIGUOUS LAYOUT
| | | *<UIView:0x7562880> - AMBIGUOUS LAYOUT
Bottom line, you have to resolve this ambiguity. If you wanted them the same height (I know you don't, but just an example), you could do:
#"V:|-[blueView][yellowView(==blueView)]-|"
Or if you wanted to say that blueView should be 44 px, and it should resize yellowView to take up the rest, it would be:
#"V:|-[blueView(44)][yellowView]-|"
If you wanted to say that blueView should be twice the height of yellowView, you can do that, too, but not with VFL:
[mainView addConstraint:[NSLayoutConstraint constraintWithItem:blueView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:yellowView
attribute:NSLayoutAttributeHeight
multiplier:2.0
constant:0.0]];
But bottom line, unless you're ok with iOS making blue and yellow any old random height (including making one zero height and the other taking up the rest of the space), you need some constraints to indicate some rules about how tall they should each be.
The reason it works when you replace the blue and yellow views with UILabel controls, is that labels have an intrinsic height constraint which will make them non-zero heights, which means that you will see both views at the same time. But with simple UIView controls, there is no intrinsic minimum height, so it's free to make either blue or yellow any size it wants to satisfy your constraints, including make one or the other a zero height.
There are tons of different options here. While you tell us you don't want blue and yellow to have the same height, you haven't described to us what will dictate their relative heights. It's hard for us to help you without some information about the rules you want to dictate the relative blue and yellow heights.
In my app i have to layout two views vertically according to each and want to make sure they look similar in iPhone 4&5.
Here's the code :
nextShowView = [[WestwoodNextShowView alloc]initWithFrame:CGRectMake(-5, 322, 290, 70)];
nextShowView.staticLabel.text = #"Next Show";
nextShowView.date.text = #"10/25";
nextShowView.address.text = #"Green Dot Tavern";
nextShowView.cityState.text = #"Detroit, MI";
[_aboutScroll addSubview:nextShowView];
playerView = [[WestwoodMusicPlayerView alloc]initWithFrame:CGRectMake(0, 407, 320, 50)];
[_aboutScroll addSubview:playerView];
NSDictionary * viewsDict = NSDictionaryOfVariableBindings(nextShowView, playerView);
NSArray * constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[nextShowView]-15-[playerView]-20-|" options:NSLayoutFormatAlignAllLeft metrics:nil views:viewsDict];
[_aboutScroll addConstraints:constraints];
What i tried to do is to give 15 point space between nextShowView and playerView and 20 point space from playerView bottom to the view bottom edge.
Please help !!!!
Thanks,
Autolayout only works if you provide all the constraints that every view needs to specify it completely. You must give not only the vertical separation between nextShowView and playerView, not only the vertical separation between playerView and the superview, but also their heights, and also their horizontal positions and widths.
If your views do not appear where you expect them, you probably have given insufficient constraints. Pause in the debugger and say po [[UIWindow keyWindow] _autolayoutTrace] at the console command line to confirm whether you have ambiguous layout.
I have a UITextView added on my UIView programmatically. The textview added is not editable, it is just to display some data. The data displayed in the textview is getting from array.The no of the textviews are same as texts which I Stored in Array.Textview is displaying from start to end one by one.I need to set the postion of textview one by one means first textview come and set at some postion,second will be set as just before first textview,third will be just before on second and so on.. Note: Something like using Animation. suppose one textview come from top and it stops at Y postion at 250 then next textview will set at just before that..it cant come upto 250 of Y postion. and so on. I have no clue how to do this. Please give me some ideas.
Don't Consider the array and the UITextView data.... its fot temporary..
NSMutableArray *array = [[NSMutableArray alloc]initWithObjects:#"One",#"Two",#"three",#"Four",#"Five",#"Six",#"Seven",#"Eight",#"Nine",#"Ten", nil];
for(int i=0;i<[array count];i++) {
UITextView *textView = [[UITextView alloc]init];
[textView setFrame:CGRectMake(20, 20*i+height, 200, height)];
[textView setBackgroundColor:[UIColor redColor]];
[textView setText:[array objectAtIndex:i]];
[self.view addSubview:textView];
}
you want to add textviews programmatically in your view based on the array,if you are adding textviews to your view then there is a problem(if u are having more number of textviews then you must take scroll view ) that's why better to take scroll view and add scroll view to your view after that you will add textviews to your scroll view ,
now take constant x-coordinate value ,width and height only change y-coordinate value and you must give the content size to scrollview .
for(int i=1;i<[array length];i++){
text_desc = [[UITextView alloc] initWithFrame:CGRectMake(63,64*i,370,60)];
text_desc.font = [UIFont fontWithName:#"Helvetica" size:13.0];
text_desc.editable=NO;
text_desc.tag=1;
}
and then set srollview content size,
scroll.contentSize = CGSizeMake(0,total*87);
//loop over the substrings of type Array to add textfields at run time
for (int i = 0; i < [substrings count]; i++)
{
CGRect frame = CGrectMake(0, i * 40, 100, 30);
UITextField * txtDynamic = [[UITextField alloc] initWithFrame: frame];
txtDynamic.text = [substrings objectAtIndex:i];
//add as subview
[view addSubview:txtDynamic];
//if you are not using ARC release the txtDynamic
}
Note
If number of UITextField more i.e it goes out of screen, then Add UITextField to UIScrollView and you can make its Size and content size dynamic.
Hope this will give you some clue.
hey mate then take one scrollview and in it just set all textview with its ContentSize like bellow
here i give an example ,
Note: this is just example here take one UIScrollView and give name scrview and after add your all this UITextField in it.
you add this code after you add data in textfield
txt1.frame = CGRectMake(txt1.frame.origin.x, txt1.frame.origin.y, txt1.frame.size.width, txt1.contentSize.height);
float txtscreen1 = txt1.frame.origin.y + txt1.contentSize.height + 10;
txt2.frame = CGRectMake(txt2.frame.origin.x, txtscreen1, txt2.frame.size.width, txt2.contentSize.height);
float txtscreen2 = txt2.frame.origin.y + txt2.contentSize.height + 10;
txt3.frame = CGRectMake(txt3.frame.origin.x, txtscreen2, txt3.frame.size.width, txt3.contentSize.height);
//.....and so on to 10
float txtscreen10 = txt3.frame.origin.y + txt3.contentSize.height + 10;
txt10.frame = CGRectMake(txt10.frame.origin.x, txtscreen10, txt10.frame.size.width, txt10.contentSize.height);
float scrollviewscreen = txtscreen10.frame.origin.y + txtscreen10.frame.size.height + 20;
scrview.contentSize=CGSizeMake(320, scrollviewscreen);//take scrollview
i hope this help you....
:)
How would I go about setting the content size for a scrollview, when the content size is dynamic. I have added all my content to a UIView named "contentView", then try calling the setcontentsize as below, but this results in no scrolling.
sudo'ish code:
[scrollView setContentSize: contentView.frame.size];
Maybe "contentView" is not stretching its size to fit its children?
Any help would be appreciated.
UIView or UIScrollView will not auto stretch based on content. You have to manually calculate the frames and position it accordingly inside the scrollview and then set the contentSize of the scrollview to the biggest possible size that can hold all its subviews.
This depends on the type of content you are going to add dynamically. So let's say you have a big text data to show, then use the UITextView and as it is a subclass of the UIScrollView, you can get the setContentSize of TextView when you assign the text content. Based on that you can set the total size of the UIScrollView.
float yPoint = 0.0f;
UIScrollView *myScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0.0f, yPoint, 320.0f, 400.0f)];
UITextView *calculatorTextView = [[UITextView alloc] init];
calculatorTextView.text = #"My looong content text ..... this has a dynamic content";
[calculatorTextView sizeToFit];
yPoint = yPoint + calculatorTextView.contentSize.height; // Bingo, we have the new yPoiny now to start the next component.
// Now you know the height of your text and where it will end. So you can create a Label or another TextView and display your text there. You can add those components as subview to the scrollview.
UITextView *myDisplayContent = [[UITextView alloc] initWithFrame:CGRectMake(0.0f, yPoint, 300.f, calculatorTextView.contentSize.height)];
myDisplayContent.text = #"My lengthy text ....";
[myScrollView addSubview:myDisplayContent];
// At the end, set the content size of the 'myScrollView' to the total length of the display area.
[myScrollView setContentSize:yPoint + heightOfLastComponent];
This works for me.
When you add stuff to your contentView call [contentView sizeToFit] and then the content view will stretch to fit its subviews, then, the code you post will work.
I'm having a scrollview as the detailedview of tableview cell. There are multiple views on the detailedview like labels, buttons etc. which I'm creating through interface builder. What I'm creating through interface builder is static. I'm putting everything on a view of height 480.
A label on my detailedview is having dynamic text which can extend to any length. The problem is that I need to set the scrollview's content size for which I need its height.
How shall I set scrollview's height provided the content is dynamic?
You could try to use the scrollview'ers ContentSize. It worked for me and I had the same problem with the control using dynamic content.
// Calculate scroll view size
float sizeOfContent = 0;
int i;
for (i = 0; i < [myScrollView.subviews count]; i++) {
UIView *view =[myScrollView.subviews objectAtIndex:i];
sizeOfContent += view.frame.size.height;
}
// Set content size for scroll view
myScrollView.contentSize = CGSizeMake(myScrollView.frame.size.width, sizeOfContent);
I do this in the method called viewWillAppear in the controller for the view that holds the scrollview. It is the last thing i do before calling the viewDidLoad on the super.
Hope it will solve your problem.
//hannes
Correct shorter example:
float hgt=0; for (UIView *view in scrollView1.subviews) hgt+=view.frame.size.height;
[scrollView1 setContentSize:CGSizeMake(scrollView1.frame.size.width,hgt)];
Note that this only sums heights, e.g. if there are two subviews side by side their heights with both be added, making the sum greater than it should be. Also, if there are vertical gaps between the subviews, the sum will be less than it should be. Wrong height confuses scrollRectToVisible, giving random scroll positions :)
This loop is working and tested:
float thisy,maxy=0;for (UIView *view in scrollView1.subviews) {
thisy=view.frame.origin.y+view.frame.size.height; maxy=(thisy>maxy) ? thisy : maxy;
}
A somewhat easier way to do this is to nest your layout within a view then put that view within the scrollview. Assuming you use tags, this works as follows:
UIScrollView *scrollview = (UIScrollView *)[self.view viewWithTag:1];
UIView *longView = (UIView *)[self.view viewWithTag:2];
scrollview.contentSize = CGSizeMake(scrollView.frame.size.width, longView.frame.size.height);
That way the longView knows how tall it is, and the scrollview's content is just set to match.
This depends on the type of content you are going to add dynamically. So let's say you have a big text data to show, then use the UITextView and as it is a subclass of the UIScrollView, you can get the setContentSize of TextView when you assign the text content. Based on that you can set the total size of the UIScrollView.
float yPoint = 0.0f;
UIScrollView *myScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0.0f, yPoint, 320.0f, 400.0f)];
UITextView *calculatorTextView = [[UITextView alloc] init]; calculatorTextView.text = #"My looong content text ..... this has a dynamic content"; `
[calculatorTextView sizeToFit];
yPoint = yPoint + calculatorTextView.contentSize.height; // Bingo, we have the new yPoint now to start the next component.
// Now you know the height of your text and where it will end. So you can create a Label or another TextView and display your text there. You can add those components as subview to the scrollview.
UITextView *myDisplayContent = [[UITextView alloc] initWithFrame:CGRectMake(0.0f, yPoint, 300.f, calculatorTextView.contentSize.height)];
myDisplayContent.text = #"My lengthy text ....";
[myScrollView addSubview:myDisplayContent];
// At the end, set the content size of the 'myScrollView' to the total length of the display area.
[myScrollView setContentSize:yPoint + heightOfLastComponent];
This works for me.
I guess there's no auto in case of scrollview, and the contentsize should be calculated for static views on the screen at least and for dynamic once it should be calculated on the go.
scrollView.contentSize = [scrollView sizeThatFits:scrollView.frame.size]
I believe would also work
I had the same situation, but then I wrote a new version in Swift 4 mirroring the better answer in Objective-C by Hannes Larsson:
import UIKit
extension UIScrollView {
func fitSizeOfContent() {
let sumHeight = self.subviews.map({$0.frame.size.height}).reduce(0, {x, y in x + y})
self.contentSize = CGSize(width: self.frame.width, height: sumHeight)
}
}