I trying to use setFrame for my UIView, but I also use auto layout on it. The trouble is I can setFrame for UIView, in spite of use setFrame(x, y, w, h) for it. I realise because of Adaptive Layout deny it, so can I setFrame for it first at runtime before Adaptive Layout adapt? Thank you
If you are trying to use Adaptive Layout together with Auto Layout you should not think in terms of frames.
However, for edge cases, the Auto Layout engine allows you to do "fine tuning" after it has calculated the position of all the elements. Something you could do in your UIViewcode is to set your layout constraints
- (void)updateConstraints {
[self applyMyViewConstraints];
[super updateConstraints];
}
After the system updates all the constraints, it will call layoutSubviews in your UIView. At this stage all the frames from the related items are set and final. You can overwrite the Auto Layout frames that you desire.
- (void)layoutSubviews {
[super layoutSubviews];
self.myView.frame = CGRectMake(0, 0, 10, 200);
}
Related
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
My UIScrollView is a ~4500px horizontal view that the user needs to scroll horizontally through to view the content.
I have set it up as follows:
- (void)viewDidLoad
{
[super viewDidLoad];
sview.frame = CGRectMake(0, 0, 568, 320);
sview.contentSize = CGSizeMake(4500, 320);
[sview setScrollEnabled:YES];
}
Yet the scroll view does nothing. Is there something obvious I missed? i've tried literally every tutorial on the web.
I got similar issue. I did following modifications and the scrollView started scrolling for me:
Select to check the 'Bounce Horizontally' property for UIScrollView
in xib.
Move the code following code to viewDidAppear instead of
viewDidLoad:
-(void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
sview.frame = CGRectMake(0, 0, 568, 320);
sview.contentSize = CGSizeMake(4500, 320);
[sview setScrollEnabled:YES];
}
I think this should help you.
I've explained it here, but there are so many answers to this problem that suggests turning off Auto Layout. That fixes the problem but that's not really the correct solution. Here's my answer:
Turning Auto Layout works, but that's not the solution. If you really need Auto Layout, then use it, if you don't need it, turn it off. But that is not the correct fix for this solution.
UIScrollView works differently with other views in Auto Layout. Here is Apple's release note on Auto Layout, I've copied the interesting bit:
Here are some notes regarding Auto Layout support for UIScrollView:
In general, Auto Layout considers the top, left, bottom, and right edges of a view to be the visible edges. That is, if you pin a view to
the left edge of its superview, you’re really pinning it to the
minimum x-value of the superview’s bounds. Changing the bounds origin
of the superview does not change the position of the view.
The UIScrollView class scrolls its content by changing the origin of its bounds. To make this work with Auto Layout, the top, left, bottom,
and right edges within a scroll view now mean the edges of its content
view.
The constraints on the subviews of the scroll view must result in a size to fill, which is then interpreted as the content size of the
scroll view. (This should not be confused with the
intrinsicContentSize method used for Auto Layout.) To size the scroll
view’s frame with Auto Layout, constraints must either be explicit
regarding the width and height of the scroll view, or the edges of the
scroll view must be tied to views outside of its subtree.
Note that you can make a subview of the scroll view appear to float (not scroll) over the other scrolling content by creating constraints
between the view and a view outside the scroll view’s subtree, such as
the scroll view’s superview.
Apple then goes on to show example of how to correctly use UIScrollView with Auto Layout.
As a general rule, one of the easiest fix is to create a constraint between the element to the bottom of the UIScrollView. So in the element that you want to be at the bottom of the UIScrollView, create this bottom space constraint:
Once again, if you do not want to use Auto Layout, then turn it off. You can then set the contentSize the usual way. But what you should understand is that this is an intended behaviour of Auto Layout.
First of all you have to add some content to UIScrollSiew as subview for scrolling,without content on UIScrollView how can you scroll?. Here is what i did,just add UIImageView to UIScrollView as subview of size same as size of UIScrollView...
In viewDidLoad method try the following code..
-(void)viewDidLoad
{
UIScrollView *scroll=[[UIScrollView alloc] init];
scroll.frame=CGRectMake(0, 0, 320, 460);
UIImageView *imageView=[[UIImageView alloc] init];
imageView.frame=CGRectMake(0, 0, 320,460);
imageView.image=[UIImage imageNamed:#"chiranjeevi.jpeg"];
scroll.contentSize = CGSizeMake(4500, 460);
[scroll setScrollEnabled:YES];
[scroll addSubview:imageView];
[self.view addSubview:scroll];
}
I tested this code it works well.I hope this code will be helpful to you..
I assume you are adding UISrollingView in your Xib file. This will work for you.
sview.delegate = self;
sview.backgroundColor=[UIColor clearColor];
[sview setCanCancelContentTouches:NO];
sview.indicatorStyle = UIScrollViewIndicatorStyleWhite;
sview.clipsToBounds = YES;
sview.scrollEnabled = YES;
sview.contentSize = CGSizeMake(320,570);
CGPoint topOffset = CGPointMake(0,0);
[sview setContentOffset:topOffset animated:YES];
Also, make sure to give IBOutlet connection in your Xib file.
I also faced the same issue.I added the scroll view in xib.I also added some subviews to this scroll view. The scroll view would stop scrolling after I added the subviews. The solution for this problem was in the xib for the view in file inspector Use Autolayout was checked. I unchecked it and the scroll view scrolled after adding the subviews.
The solution was uncheking the Use Autolayout in file inspector in xib.
The problem is, that I do have a view containing 3 parts.
The first part is a simple header - same height always!
The second part is a simple description line - same height always!
Now the problem is the third part. It's a viewContainer for dynamically calculated subviews (each of them having a custom controller). The height of the content is dynamic caused by some text information downloaded from a backend. So some times I would need to scroll to be able to read the whole text, sometimes not.
Currently I am doing it this way:
Calculate the size of the UILabel for a specific text.
Then resize the parent view so fit the UILabel (if smaller).
Then resize the scrollView of my 3-Part-ViewController-View to fit its subviews.
The detail viewController with the dynamic content:
self.labelDescription.text = self.customData.descriptionText;
[self.labelDescription sizeToFit];
if(self.view.frame.size.height < (self.labelDescription.frame.size.height + self.labelDescription.frame.origin.y)) {
CGRect newSize = CGRectMake(0,
0,
self.view.frame.size.width,
self.labelDescription.frame.size.height +
self.labelDescription.frame.origin.y);
self.view.frame = newSize;
}
The resizing of the scroll view after adding and resizing my detail view:
[self addChildViewController:controllerCustomData];
[self.scrollView addSubview:controllerCustomData.view];
CGRect newRect = CGRectMake(0,
self.viewElementDetailContentContainer.frame.origin.y,
controllerCustomData.view.frame.size.width,
controllerCustomData.view.frame.size.height);
controllerCustomData.view.frame = newRect;
self.scrollView.contentSize = CGSizeMake(self.scrollView.contentSize.width, newRect.origin.y + newRect.size.height);
So my question is, are there easier ways to do this?
You could create a method that does everything you need. You'll need to write the method (maybe methodS) once and use it passing the necessary arguments.
I had a lot of trouble resizing a UITableView to fit between an UINavigationBar and a UITabBar. My implementation involved creating a custom frame in viewDidAppear(), and then setting the UITableView to an appropriate frame. Although this solution works well, it doesn't work perfectly- the screen has a little spasm every time the view is loaded. I figured the issue was due to the UITableViewb becoming fullscreen, as it wants to, and then me resizing it, in front of the user's eyes. However, I have no idea how else to implement what I want to: resizing the UITableView to fit into the screen properly. Here's my implementation in viewDidAppear():
- (void)viewDidAppear:(BOOL)animated{
[self.view.superview addSubview:navigationBar];
CGRect frame = self.view.frame;
frame.size.height = self.view.frame.size.height - navigationBar.frame.size.height;
frame.origin.y = self.view.frame.origin.y + 44;
self.tableView.frame = frame;
[super viewDidAppear:animated];
}
It's very hard to see the effect when recorded on video, and probably terrible in GIF form, but here's a little GIF I recorded of the flash being induced.
Here is the result if I use that same code in viewWillAppear() instead:
Thank you!
I had this problem, turned out it was an issue with auto layout on the view. In the Interface Builder, switch to the File Inspector property view and make sure 'Use Autolayout' is unchecked.
After this, you can consistently resize your UITableView in the viewWillAppear method without the glitches.
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
CGRect frame = [tableView frame];
frame.size.height = 365;
self.tableView.frame = frame;
}
Why using viewDidAppear, by the time this method gets called your view is visible on screen then you will of course receive a glitch.
What you have to do is change the method from viewDidAppear to viewWillAppear.
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.