my UIView has a dynamic amount of views inside of it and also has a background color to it. I'm trying to tuck the UIView in around all the items, but it's not working.
[self sizeToFit] Seems to work just fine on a UILabel, but not on a UIView.
I know I can calculate the area manually by looping through all the subviews, but that just seems like too much work for something like this.
I'm hoping someone knows a simpler way.. Thanks
UPDATE
If you're using auto layout, you can make your container view fit its subviews snugly. Set up constraints between your container and its subviews, and set up constraints between the subviews and the container's superview, but do not set up constraints between the container and its superview.
ORIGINAL
There's no built-in support for automatically shrink-wrapping a view around its subviews.
The sizeToFit method works by sending [self sizeThatFits:self.bounds.size], then resizing itself based on the size returned by sizeThatFits:.
UILabel overrides the sizeThatFits: method to compute the size of its text in its font.
You will need make a custom UIView subclass and override sizeThatFits:. It's pretty simple:
- (CGSize)sizeThatFits:(CGSize)size {
CGRect rect = CGRectZero;
for (UIView *subview in self.subviews)
rect = CGRectUnion(rect, subview.frame);
return rect.size;
}
I don't think there is a "dynamic" way. You see, resizing only works the way around, a subview ca be made to dynamicaly change it's size when a superview does.
Anyway, what you are describing as "too much work" is exactly (more or less) what sizeToFit does...
Related
I’ a little confused with UIView’s bounds and frame. I’m creating UIButton manually with the same size as its super view:
UIButton *button = [[UIButton alloc]initWithFrame:aView.bounds];
button.backgroundColor = [UIColor blueColor];
[aView addSubview:button];
But in the simulator the button and its super view have different size. I don’t really understand this..
(I didn't use IB to create the button because next I’d like to create a 2D array of buttons within that super view, so I think maybe it’s better to create them manually)
I strongly suspect you're putting you code in viewDidLoad before the view has has a chance to finish laying out its subviews. Move your code to viewDidLayoutSubviews instead.
You are correct to use aView.frame rather than aView.bounds. Both will give you the size (width and height of the aView) but frame gives you the position of aView in its superview. The button will need the position relative to aView not aView's superview. Bounds will give you aView's position in its own coordinate system (most likely 0, 0).
What could be happening is you are creating the button using aViews size before aView's frame has been determined. When is aView's frame being determined? Is aView being created via interface builder? When are you creating the button?
The code you provided does work - that is the correct way to make a subview the same size as its superview. There are two possible explanations for why you think that isn't happening:
You might be misinterpreting what you see in the simulator. The view you see behind the button might not be aView. I'm going to discount that possibility and go with the second possibility:
You are running that code before aView achieves its final size. In other words, you are inserting button, but then layout comes along and changes the size of aView. That change in the size of aView doesn't change the size of button, so button is left with the size aView used to have at the time you created and inserted it.
I'm asking this (somehow) simple question just to be finicky, because sometimes I'm worried about a misuse I might be doing of many UIView's APIs, especially when it comes to autolayout.
To make it super simple I'll go with an example, let's assume I need an UIView subclass that has an image icon and a multiline label; the behaviour I want is that the height of my view changes with the height of the label (to fit the text inside), also, I'm laying it out with Interface builder, so I have something like this:
with some constraints that give fixed width and height to the image view, and fixed width and position (relative to the image view) to the label:
Now, if I set some text to the label, I want the view to be resized in height to fit it properly, or remain with the same height it has in the xib.
Before autolayout I would have done always something like this:
In the CustoView subclass file I would have overridden sizeThatFits: like so:
- (CGSize) sizeThatFits:(CGSize)size{
//this stands for whichever method I would have used
//to calculate the height needed to display the text based on the font
CGSize labelSize = [self.titleLabel intrinsicContentSize];
//check if we're bigger than what's in ib, otherwise resize
CGFloat newHeight = (labelSize.height <= 21) ? 51: labelSize.height+20;
size.height = newHeight;
return size;
}
And than I would have called something like:
myView.titleLabel.text = #"a big text to display that should be more than a line";
[myView sizeToFit];
Now, thinking in constraints, I know that autolayout systems calls intrinsicContentSize on the view tree elements to know what their size is and make its calculations, therefore I should override intrinsicContentSize in my subview to return the exact same things it returns in the sizeThatFits: method previously shown, except for the fact that, previously, when calling sizeToFit I had my view properly resized, but now with autolayout, in combination with a xib, this is not going to happen.
Of course I might be calling sizeToFit every time I edit text in my subclass, along with an overridden intrinsicContentSize that returns the exact same size of sizeThatFits:, but somehow I don't think this is the proper way of doing it.
I was thinking about overriding needsUpdateConstraints and updateConstraints, but still makes not much sense since my view's width and height are inferred and translated from autoresizing mask from the xib.
So long, what do you think is the cleanest and most correct way to make exactly what I show here and support fully autolayout?
I don't think you need to define an intrinsicContentSize.
Here's two reasons to think that:
When the Auto Layout documentation discusses intrinsicContentSize, it refers to it as relevant to "leaf-views" like buttons or labels where a size can be computed purely based on their content. The idea is that they are the leafs in the view hierarchy tree, not branches, because they are not composed of other views.
IntrinsicContentSize is not really a "fundamental" concept in Auto Layout. The fundamental concepts are just constraints and the attributes bound by constraints. The intrinsicContentSize, the content-hugging priorities, and the compression-resistance priorities are really just conveniences to be used to generate internal constraints concerning size. The final size is just the result of those constraints interacting with all other constraints in the usual way.
So what? So if your "custom view" is really just an assembly of a couple other views, then you don't need to define an intrinsicContentSize. You can just define the constraints that create the layout you want, and those constraints will also produce the size you want.
In the particular case that you describe, I'd set a >=0 bottom space constraint from the label to the superview, another one from the image to the superview, and then also a low priority constraint of height zero for the view as a whole. The low priority constraint will try to shrink the assembly, while the other constraints stop it from shrinking so far that it clips its subviews.
If you never define the intrinsicContentSize explicitly, how do you see the size resulting from these constraints? One way is to force layout and then observe the results.
Another way is to use systemLayoutSizeFittingSize: (and in iOS8, the little-heralded systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority:). This is a closer cousin to sizeThatFits: than is intrinsicContentSize. It's what the system will use to calculate your view's appropriate size, taking into account all constraints it contains, including intrinsic content size constraints as well as all the others.
Unfortunately, if you have a multi-line label, you'll likely also need to configure preferredMaxLayoutWidth to get a good result, but that's another story...
My app has a UIView called stepView that needs to grow and shrink, always expanding down and to the right, and always shrinking up and to the left. It has a subview, durationLabel, which runs along the entire width of the stepView and has a fixed height.
When the stepView is animated to grow larger, the label grows properly along with it: the text and the label's background slide to the right in sync with the growing stepView. However, when the stepView is animated to grow smaller, it immediately snaps to its new size, leaving a gap between the stepView's shrinking right edge and its right edge until the animation completes.
The label initialization code in stepView.m:
CGRect duration_frame = CGRectMake(0, 0, self.frame.size.width, HEADER_HEIGHT);
self.durationLabel = [[UILabel alloc] initWithFrame:duration_frame];
[self.durationLabel setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
[self addSubview:self.durationLabel];
And the animation code:
[UIView animateWithDuration:ANIM_DURATION
animations:^{
[stepView setFrame:newFrame];
}
];
Using bounds/center instead of frame produces the same effect. I've considered using a transform instead, but the code for calculating "newFrame" is somewhat involved, so I'd rather avoid rewriting it if possible. I've also tried manually changing the label's frame in the same animation block, but that simply makes it disappear entirely (possibly because I was trying to animate both a view's frame and its subview's frame in the same animation block?). I've also confirmed stepView and its subviews aren't undergoing any other animations at the same time, although there are animations happening to unrelated views in separate animation blocks.
I'm at a loss for why the label should animate perfectly when the stepView grows but fail to animate at all when it shrinks. I haven't seen anything in the documentation that indicates there would be a difference. Thank you for any help you can provide.
I think this is an issue with UILabel - using another UIView as subview instead of an UILabel the animation works fine also when you shrink.
If I understand correctly what you need to obtain as result, however, I would suggest you to try the following:
don't set the autoresizingMask property
// [self.durationLabel setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
set the stepView clipToBounds property to YES
self.clipToBounds = YES;
This way the UILabel frame will not change but only the part which is actually within the stepView bounds will be visible. I hope this might help.
Update: just done a quick research and found that the question is already answered here resize uiview with UILabel and animate it correctly - the answers provided seem to be aligned to what I've suggested above.
I have one UIView and inside that, N UILabels which are laid out relative to each other.
The containing UIView has a background color, I want to extend the UIView to be high enough to cover all labels inside it, so the background color is behind them all.
(I'm embedding them in a UIView so I can have the labels inset from the view edges.)
Is there away to make the UIView's height expand to fill its content? I can't figure it from the constraint options, it seems like its all relative to superviews.
Normally I'd just work out the frame sizes programatically in viewDidAppear but those are getting reset by the constraints system, AFAIK.
I think I actually worked it out though.
I had the labels height set manually from when I drag-dropped and resized it. Deleting the height constraint on the UILabel made it size to fit content, which causes its superview to resize too. At least I think that's the case, I'm new to constraints.
Will leave the question up since it will probably bite someone else too.
a UIScrollView works just fine by it's self (in IB). However, once you put the UIScrollView inside of a UIVIew in IB, it no longer works. I'm doing this with no subclassing so what's the deal?
Answer
You have to make sure Autoresize Subviews is checked on the containing UIView.
The next part is a bit of a hack. You need to assign a new frame to the containing UIView as well. However, the new frame CANNOT be the pre-existing size of your UIView. It must be a different size for this to work.
If you want the size contained on your UIView in InterfaceBuilder then you need to do something like this:
self.frame = CGRectMake(0,0,0,0);
self.frame = [put your desired rect size here];
Have you tried returning NO in touchesBegan if the touch falls inside your scroll view's frame?
Edit:
Make sure you set the scroll view's content size ([[self scrollView] setContentSize:CGSizeMake(320,480)]; - the values do not matter) and make sure you check "Bounce Horizontally" and/or "Bounce Vertically" (depending on which you want) as well as "Bounces":
Have you tried to set contentsize property of UiScrollView like this
self.scrollView.contentSize=CGSizeMake(320,860);