UILabel sizeToFit and constraints - iphone

Is there any simple way which can help me to change position of dependent views dynamically using their content size?
I want to show several views in column which all have varying content. And I want them to be placed one after another (I've created layout using constraints which looks like this)
But whenever I change content of labels and call sizeToFit, system seems to ignore layout.
At the moment I'm interested only in height property, I know that constraining rect can be used too and in the past I wrote many categories on UIView to change sizes dynamically (I guess everyone did). But maybe there is a simple way which I don't know?

-sizeToFit should not be called if you are using auto-layout. That's part of the 'old' system.
It looks like IB has inserted explicit heights into your constraints (the vertical bars next to the labels indicate this). Try selecting the labels and hitting Cmd+= to clear these.
For multiline labels you will also need to do the following in your view controller to make everything work correctly when rotating/resizing the view:
- (void)updateLabelPreferredMaxLayoutWidthToCurrentWidth:(UILabel *)label
{
label.preferredMaxLayoutWidth =
[label alignmentRectForFrame:label.frame].size.width;
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
[self updateLabelPreferredMaxLayoutWidthToCurrentWidth:self.label1];
[self updateLabelPreferredMaxLayoutWidthToCurrentWidth:self.label2];
[self updateLabelPreferredMaxLayoutWidthToCurrentWidth:self.label3];
[self.view layoutSubviews];
}
Multiline labels expose one of the weaknesses of auto-layout. We have to update preferredMaxLayoutWidth to force the label to reflow and adjust its height, otherwise if the view is resized/rotated, auto-layout does not realize the label needs to be reflowed and resized.

If you still want to use Auto Layout Constraint for your Label. This is a solution:
[self.lblBadgeValue sizeToFit];
self.constraintWidthBadgeLabel.constant = self.lblBadgeValue.frame.size.width;
[self.lblBadgeValue needsUpdateConstraints];
[self.lblBadgeValue layoutIfNeeded];
Explain more:
sizeToFit -> make label fit height, width with
content of it.
So in runtime you need to update constraint height,
or width for label
After that you need to say for compiler know
that what need update constraint.
And in the end you need to call
layout if have change on constraint.

This works too.
https://github.com/jszumski/auto-layout-table-cells/blob/master/DynamicCellHeights/JSLabel.m
#interface JSLabel : UILabel
#end
#implementation JSLabel
- (id)init {
self = [super init];
// required to prevent Auto Layout from compressing the label (by 1 point usually) for certain constraint solutions
[self setContentCompressionResistancePriority:UILayoutPriorityRequired
forAxis:UILayoutConstraintAxisVertical];
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
self.preferredMaxLayoutWidth = CGRectGetWidth(self.bounds);
[super layoutSubviews];
}
#end

Related

iOS - display UICollectionViewCells on a single row

I am trying to display all the cells on my UICollectionView on a single row with an hozizontal scroll.
For this, I am using the built in UICollectionView class but I created a subclass of UICollectionViewFlowLayout with the following code:
#implementation MemberCollectionViewFlowLayout
- (id)init {
if ((self = [super init])) {
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.minimumLineSpacing = 10000.0f;
}
return self;
}
- (CGSize)collectionViewContentSize
{
NSArray *memberArray = [[NSUserDefaults standardUserDefaults] objectForKey:#"currentGroupMembers"];
return CGSizeMake([memberArray count]*50 , self.collectionView.frame.size.height);
}
#end
As a result I get a scrollable row but the dont display if the row is larger than the screen. If I increase the height on the contensize I can see that the remaining cells are added on a second line.
How can I make sure that all my cells are on a single line?
Many thanks for your help
In the flow layout documentation it says:
For a horizontally scrolling grid, this value represents the minimum spacing between successive columns.
So if you are adding a huge value horizontally and blowing the horizontal dimensions up - much larger than the content size you set. Therefore, the flow layout will try to start the next line.
Maybe it is not necessary to subclass at all. You could set the properties of the flow layout without subclassing and let the collection view take care of the content size. With the constraints of the view and the scroll direction, it should have the desired effect.

UIScrollView problem, scrolls just on one side

I'm fairly new to programming, and I have looked for an answer for a very long time. There are some posts about it, but nothing has solved my problem. I have a UIScrollView view that I get from the nib, everything is ok with this, the content length is good and scrolling works, but it just scrolls on the left side, if I try to scroll on the right side it doesn't scroll..
Here is the code,
- (void)viewDidLoad {
[super viewDidLoad];
NSString *descriptionString = _currentBook.description;
CGSize stringSize = [descriptionString sizeWithFont:[UIFont boldSystemFontOfSize:16] constrainedToSize:CGSizeMake(387, 9999) lineBreakMode:UILineBreakModeWordWrap];
_authorLabel.text = _currentBook.author;
_titleLabel.text = _currentBook.title;
_descriptionLabel.text = [NSString stringWithFormat:#"Description: %#",_currentBook.description];
[(UIScrollView *)self.view setContentSize:CGSizeMake(387, stringSize.height +50)];
}
Thanks in advance!
Its hard to understand the problem since we cant see your nib file, but its better practice to put the scrollView on top the view of the view controller, and connect it to an IBOutlet in your view controller.
In order to find the problem I would get rid of the textfields for testing purposes (I think the constrained 9999 might be a problem but I am not sure) and then print and post the frame of the scrollView and the Content size in runtime.I am betting that you will see some issue with the frame of the uiscrollview.
Thanks,
Ok, after copy-pasting and running some tests, I found out the problem.
The problem is in the wording of the question, your problem is not that the "scroll doesn't work on the right side" (As in: you move your finger up and down on the right side of the screen without triggering a scroll), the problem is that the contents, the label itself is going out of bounds, outside of the visible area of the scrollView, and the right-handed side is not visible.
First of all, you should note that the iphone resolution is 320x480 (640x960 for Retina), so you actually have to work with a smaller width (using 387 width will make it go out of bounds).
Second, take in account the x position of the label itself is also affecting the amount of visible text. With this in mind, a more generic code would be:
- (void)viewDidLoad
{
[super viewDidLoad];
// This number represents the total width of the label that will fit centered in
// the scrollView area.
CGFloat visibleWidth = self.view.frame.width - descriptionLabel.frame.origin.x*2;
// Use the number above to get a more accurate size for the string.
NSString *descriptionString = _currentBook.description;
CGSize stringSize = [descriptionString sizeWithFont:[UIFont boldSystemFontOfSize:16] constrainedToSize:CGSizeMake(visibleWidth, 9999) lineBreakMode:UILineBreakModeWordWrap];
// Fill data (why so many underscores?)
_authorLabel.text = _currentBook.author;
_titleLabel.text = _currentBook.title;
_descriptionLabel.text = [NSString stringWithFormat:#"Description: %#", _currentBook.description];
// Also, why didn't you resize the description label? I'm assuming that you forgot this.
// (Make sure that the descriptionLabel number of lines is 0)
CGRect frame = descriptionLabel.frame;
descriptionLabel.frame.size = stringSize;
// Now set the content size, since you're using the scrollView as the main view of the viewController,
// I'll asume that it's using the whole screen, so I'm gonna use the whole width
// of the screen (that's what UIScreen is for).
[(UIScrollView *)self.view setContentSize:CGSizeMake([UIScreen mainScreen].bounds.size.width, stringSize.height +50)];
}
Finally i've found my problem, I've added a uiview programmatically as a subview to some view and then to this View i've added my scroll view as a subview.. And then it would only scroll in the area of my UIView. It was nonsense to do it like this, still a lack of knowledge..
Thank you all for trying to help!

How to find out the exact frame of a UIViewController's view?

When is it possible to know the exact size of a UIViewController's view?
The Problem
I have a multi-line UILabel whose frame depends of its text and the width of the parent view. Given that I need to position other views below the UILabel, it's important to make its frame cover exactly the space of the text.
I currently calculate the size like this on viewDidLoad:
labelSize = [text sizeWithFont:font constrainedToSize:CGSizeMake(self.view.frame.size.width, MAX_HEIGHT)];
The problem is that the width of the parent view changes when the UIViewController is used as a modal form sheet or a popover. If I use autoresizingMask the frame of the UILabel is adjusted accordingly, but it no longer is an exact fit for the text.
Where can I calculate the frame of this UILabel knowing the exact size of the UIViewController's view?
Debugging Efforts
This is the result of printing self.view.frame when showing the UIViewController as a modal form sheet (UIModalPresentationFormSheet).
viewDidLoad: (0.000000;0.000000;320.000000;480.000000)
viewWillAppear: (0.000000;0.000000;768.000000;960.000000)
afterDelay: (0.000000;0.000000;540.000000;576.000000)
The code that produces the above output:
- (void)viewDidLoad {
[super viewDidLoad];
[Utils logFrame:self.view.frame tag:#"viewDidLoad"];
}
- (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[Utils logFrame:self.view.frame tag:#"viewWillAppear"];
[self performSelector:#selector(afterDelay) withObject:nil afterDelay:0.1];
}
- (void) afterDelay {
[Utils logFrame:self.view.frame tag:#"afterDelay"];
}
That's because your UIViewController's root view is using autoresizingMask. I don't think there is a "perfect timing" to detect the size of your UIViewController's root view unless you override your root view's 'layoutSubviews' method.
If you don't want your view to be automatically resized, just don't use autoresizing and set the size by yourself, it will always be the same size you expected.
If you are not sure what kind of autoresizingMasks your root view is using, NSLog is your friend.
This looks like a bug, the value should be known in viewWillAppear.
The alternative is to subclass your UIView and override layoutSubviews there to arrange the subviews based on content. You'll then have to call [self.view setNeedsLayout] from your view controller every time you update the contents of the label. The system will call it if the view resizes, so that should have you covered.
Try this for finding the label size,
labelSize = [text sizeWithFont:font constrainedToSize:CGSizeMake(self.label.frame.size.width, MAX_HEIGHT)];
Instead of using the view's width, use the label's width in the constrain size. This should be available in the viewDidLoad, viewDidApper methods of the view controller.
try viewWillAppear. There is no reason for not knowing the size before the the view is about to shown

Relativelayout or LinearLayout in ios iphone development?

I want to add a subview in the top of my view, I have to recalculate the origin y value for all of other views and re-position them to leave space for the new added view.
It is very boring, as I know android have relativelayout or linearlayout can help automatically do that.
How to solve this problem easily in ios development?
I've created a library to solve just this problem: CSLinearLayoutView
You use it like this:
// create the linear layout view
CSLinearLayoutView *linearLayoutView = [[[CSLinearLayoutView alloc] initWithFrame:self.view.bounds] autorelease];
linearLayoutView.orientation = CSLinearLayoutViewOrientationVertical;
[self.view addSubview:linearLayoutView];
// create a layout item for the view you want to display and add it to the layout view
CSLinearLayoutItem *item = [CSLinearLayoutItem layoutItemForView:someView];
item.padding = CSLinearLayoutMakePadding(5.0, 10.0, 5.0, 10.0);
item.horizontalAlignment = CSLinearLayoutItemHorizontalAlignmentCenter;
item.fillMode = CSLinearLayoutItemFillModeNormal;
[linearLayoutView addItem:item];
// add more items
I've been trying to do a relative (linear) layout for a while and finally decided to just subclass UIScrollView to get it done.
I started out just replacing layoutSubviews with a simple loop through the subviews that reset the origins while keeping a running Y. But, some unexpected things are added to the scrollview, including UIInlineAutoCorrect views from textfields/views, which means these things were being mangled by the layout. So I added a little bit of logic that uses the tag property of a UIView to determine if I should lay it out:
-(void) layoutSubviews{
CGFloat runningY = 0.0f;
CGFloat widestWidth = 0.0f;
for (UIView *view in self.subviews) {
if (view.tag != 1999) {
continue;
}
view.origin = CGPointMake(view.origin.x, runningY);
runningY += view.height;
if ([view autoresizingMask] == UIViewAutoresizingFlexibleWidth) {
view.width = self.width;
}
if (view.width > widestWidth) {
widestWidth = view.width;
}
}
[self setContentSize:CGSizeMake(widestWidth, runningY)];
}
If you would still like to use unique tags for your views, you should just specify a range of tags that will be included in the layout instead of a single value.
It's not much work to subclass UIView to make sense of methods like -(void)addView:toRightOfView: etc. You could do this as you go, porting only the methods you need. You could then call these in your override of layoutSubviews as Benjamin indicates.
Views can be built using IB or they can be written programmatically; Android scores well here in making layouts readable and you can bring that benefit to iOS views created programmatically. That there are few iOS devices means beyond readability there are not (yet?) many practical benefits to this pattern.
NB. A "XIB" file is an XML file. Open it up in your favourite text editor and take a look.
** EDIT.
Here's a quick example I knocked up. It has not been tested but some thing like this will work in your subclass of UIView (call it UIRelativeView perhaps).
- (void) addSubview:(UIView *) viewOne
toRightOfSubview:(UIView *) viewTwo
{
if (viewTwo == nil ||
[self.subviews contains:viewTwo] == NO)
{
[self addSubview:viewOne];
}
else
{
CGRect frameTwo = viewTwo.frame;
CGPoint originOne = CGPointMake(frameTwo.origin.x + frameTwo.size.width,
frameTwo.origin.y);
CGRect frameOne = CGRectZero;
frameOne.origin = originOne;
frameOne.size = viewOne.frame.size;
[viewOne setFrame:frameOne];
[self addSubview:viewOne];
}
}
- (void) moveSubview:(UIView *) viewOne
toRightOfSubview:(UIView *) viewTwo
{
if (viewTwo == nil ||
[self.subviews contains:viewTwo] == NO)
{
[self addSubview:viewOne];
}
else if ([self.subviews contains:viewOne] == NO)
{
[self addSubview:viewOne toRightOfSubview:viewTwo];
}
else
{
CGRect frameTwo = viewTwo.frame;
CGPoint originOne = CGPointMake(frameTwo.origin.x + frameTwo.size.width,
frameTwo.origin.y);
CGRect frameOne = CGRectZero;
frameOne.origin = originOne;
frameOne.size = viewOne.frame.size;
[viewOne setFrame:frameOne];
}
}
You've got no luck here. iOS doesn't have provisions for positioning the views in different layouts like Android. You need to reposition all the other subviews to make the way for the new view.
There are some view resizing methods like sizeToFit and autoResizingMask but they won't help you in your case here.
iOS is much more focused on pixel accuracy than Android it is, which uses relative layouts as it has to deal with multiple screen sizes. However, in iOS, the Interface Builder is an incredibly good tool included in XCode, which you can use.
Also, if you are just adding subviews in a repetitive manner, you could override the layoutSubviews method and use that to handle to manual labour for you. You mention having to "recalculate the origin y value for all of other views and re-position them to leave space for the new added view" ... You could code that into your layoutSubviews so you don't have to do it yourself each time.
Unfortunately, though, the SDK doesn't have any of this included by default. autoresizingMask's are great but you can't use that for initial layout; it's for automatic really it when rotating only.
As of iOS 9 you can use UIStackView, which works very similarly to LinearLayout: you add views and the stack view arranges them as needed based on your sizing preferences:
Fill will leave three of them their natural size, and make the fourth one take up the most space. It uses Auto Layout's content hugging priority to decide which one to stretch.
Fill Equally will make each subview the same size so they fill all the space available to the stack view.
Fill Proportionally uses the intrinsic content size of each subview to resize them by an equal amount. So view 1 was designed to have twice as much height as views 2, 3 and 4, that ratio will remain when they are resized – all the subviews get proportionally bigger or smaller.
Equal Spacing does not resize the subviews, and instead resizes the spacing between the subviews to fill the space.
Equal Centering is the most complicated, but for many people also the most aesthetically pleasing. It attempts to ensure the centers of each subview are equally spaced.
You can also set spacing between views in the stack view, adding some padding.
WARNING: When adding stack view child views in code you should always use addArrangedSubview() like this:
stackView.addArrangedSubview(someView)
If you try to use plain old addSubview() it won't work correctly, because the stack view won't know to arrange it.
As for removing, you need to be careful to use stackView.removeArrangedSubview(someView) and someView.removeFromSuperview() otherwise the view won't be removed correctly.
You might find my UIStackView tutorial useful.

How to change the size of the labels on a UITableViewCellStyleValue2 cell? (UITableView iPhone)

I'm using a UITableViewCellStyleValue2 cell in my UITableView in my app and I want the left column (the blue text - self.textField) to be much narrower than the default. I've tried setting the self.textField.bounds after creating the cell but this doesn't seem to work - looking in the debugger it appears the bounds haven't been set.
Can I edit the default cell type in this way, or should I just make my own subclass of UITableViewCell?
Thanks.
You can subclass UITableViewCelland still use UITableViewCellStyleValue2. You can then overwrite the layoutSubviews method to change the size of the labels:
- (void) layoutSubviews {
[super layoutSubviews]; // layouts the cell as UITableViewCellStyleValue2 would normally look like
// change frame of one or more labels
self.textLabel.frame = CGRectMake(...);
self.detailTextLabel.frame = CGRectMake(...);
}