Understanding scaling sizes for UIViews and objects - ios

I am trying to better understand how to control the size of Views in terms of older iphones and the new iphones difference in size.
If I have a design like the below example, how should this be coded programatically in terms of the subviews.
Excuse the badly drawn diagram but it should help to explain.
In this example, the fieldView and buttonView would always need to remain a fixed size as they have objects which would not look great when made smaller. However the logoview has another sub view for the logo itself, so could be shrinked depending on device/screen size.
How would this be accomplished? Setting up the example subviews programatically. The part I do not understand is that in viewDidLoad where the subviews are created would you not have to create in order like this:
-(void)ViewDidLoad {
CGRect screen = [[UIScreen mainScreen] applicationFrame];
wholeView = [[UIView alloc] initWithFrame:CGRectMake(self.view.bounds.origin.x, self.view.bounds.origin.y, self.view.bounds.size.width, self.view.bounds.size.height)];
logoView = [[UIView alloc] initWithFrame:CGRectMake(0,0,320,150);
fieldView = [[UITableView alloc] initWithFrame:CGRectMake(0, logoView.bounds.size.height, 320, 100);
I understand about using
autoresizingMask
but how would it come into use in terms of working out a height depending on the actual view size available?

Not sure I understand your doubts, but adding the following should do the trick:
self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
logoView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
The remaining view would keep both their margins and height/width fixed (i.e., the default value of UIViewAutoresizingNone for their autoresizingMask is fine.)
how would it come into use in terms of working out a height depending on the actual view size available?
basically, what you see on your iPhone display is a hierarchy of views; the topmost view in this hierarchy is a UIWindow. This has the same size as the device screen (it is initialized that way).
Now, in the code above, our logoView has fixed margins: this means that it will remain at the same distance from the container view frame; if we specify that is size is flexible, then the logoView will simply occupy the whole space to keep the margins fixed.

Related

How to do a bottom UIToolbar with AutoLayout?

I'm trying to add a UIToolbar to the bottom of a screen within my app. But I'm having some difficulties.
This is the code:
self.toolBar = [[UIToolbar alloc] initWithFrame:CGRectMake(0.0f, self.view.frame.size.height-44.0f, self.view.frame.size.width, 44.0f)];
self.toolBar.backgroundColor = [UIColor whiteColor];
[self.view addSubview:self.toolBar];
However, when the device is rotated the UIToolbar is offscreen. How do I use NSAutoLayout solve this?
You could add
self.toolBar.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
That will generate the correct constraints, since by default for code-created UIViews, translatesAutoresizingMaskIntoConstraints is turned on. A resizing mask of 0 will almost never do what you want, so if you are setting frames yourself, you always need to set the mask correctly as well (since that determines what will happen to the frame when the superview size changes). The pattern would be 1) set frame; 2) set mask; 3) call addSubview.
Alternatively, you could set translatesAutoresizingMaskIntoConstraints to NO, don't bother with calculating a frame, and create equivalent layout constraints yourself (height constraint of 44, left/right/bottom constraints to touch the superview).

UIScrollView won't scroll to reveal more content

I've been working on this for the past couple of hours and can't figure this out.
I have a scrollview with content added to it as a subview, but my scroll view won't scroll past the tabBarController, even though the content goes beyond this point.
self.scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 320, self.view.bounds.size.height)];
self.scrollView.contentSize = CGSizeMake(320, self.scrollView.frame.size.height);
self.scrollView.autoresizesSubviews = YES;
self.scrollView.delegate = self;
[self.view addSubview:self.scrollView];
I figured this would work, but definitely doesn't. I've been searching for an answer and can't find one, help is much appreciated!
If your scrollviews frame and contentSize are the same height, it won't be able to scroll, the contentSize should be higher than your scrollview frame in order to make the scrollerview be able to scroll.
JonLOo’s answer covers the original question. To address your new question:
How do I get the scroll view to automatically adjust how much scroll is needed based on how much content there is?
Read through Apple’s Technical Note TN2154:
UIScrollView And Autolayout, particularly the Pure Auto Layout Approach section. Essentially, the constraints on the content views must fully specify a size, which the scroll view uses as its content size.
On method: viewDidLoad you will get different height of your scrollView. Try to get on viewDidAppear
Try also:
scrollView.scrollEnabled = YES;

Get actual dimensions of iOS view

How would I get the actual dimensions of a view or subview that I'm controlling? For example:
UIView *firstView = [[UIView alloc] initWithFrame:CGRectMake(0,0,200,100)];
[self addSubview:firstView];
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(10, 20, 230, 120)];
[firstView addSubview:button];
As you can see, my button object exceeds the dimensions of the view it is being added to.
With this is mind (assuming there are many objects being added as subviews), how can I determine the actual width and height of firstView?
If you would like to have the button have the same width and height as firstView you should use the bounds property of UIView like this:
UIView *firstView = [[UIView alloc] initWithFrame:CGRectMake(0,0,200,100)];
[self addSubview:firstView];
UIButton *button = [[UIButton alloc] initWithFrame:firstView.bounds];
[firstView addSubview:button];
To just get the width/height of firstView you could do something like this:
CGSize size = firstView.bounds.size;
NSInteger width = size.width;
NSInteger height = size.height;
The first view really is 200 x 100. However, the views inside are not clipped unless you use clipsToBounds.
A preferable solution to your problem is to stop adding views that exceed the dimensions or to stop resizing views so that they overflow the available space, as appropriate.
(To actually calculate the full bounds including all subviews in the hierarchy is not hard, just a lot of work that won't actually get you anywhere. UIView happens to allow views to be larger than their containers, but it doesn't necessarily give any guarantees about what will work correctly and is likely to be a source of bugs if taken advantage of. The reason for clipsToBounds is probably that clipping is expensive but necessary during animation where subviews may be temporarily moved out of bounds for the purposes of an effect before they are removed from the view.)
firstview.frame.size.width
firstview.frame.size.height

How to understand the behavior of table.autoresizingMask?

I have learned that the proper way to set a UITableView that covers up the whole main view (full width and full height), in a Single View app is, in viewDidLoad:
table = [[UITableView alloc] initWithFrame:self.view.bounds];
table.autoresizingMask = UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleHeight;
[self.view addSubview:table];
note that if a physical iPad 2 is held at Landscape mode, and if the self.view.bounds above is printed inside viewDidLoad, it will show: {{0, 0}, {768, 1004}}. So I thought the idea is: don't worry the width and height not being correct, because the UIViewAutoresizingFlexibleWidth and UIViewAutoresizingFlexibleHeight will take care of setting the correct width and size automatically.
So I actually tried replacing the above first line by:
table = [[UITableView alloc] init];
or
table = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
or even
table = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 1004, 768)];
or the ultimately "correct value":
table = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 1024, 748)];
when the iPad 2 is held at Landscape position. But now the table won't fully expand to the whole main view. So what is the governing principle here? The width and height can be set incorrectly, but it must be incorrectly at {768, 1004}? It can't be "incorrect" with other values? If no CGRect was given, or some dummy values {200, 200} was given, what should the code in viewDidLoad do to make the table have the full main view's width and height whether it is Landscape or Portrait mode?
dont use bounds, use frame instead:
table = [[UITableView alloc] initWithFrame:self.view.frame];
table.autoresizingMask = UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleHeight;
UIAutoresizingFlexibleWidth means that the view's width will expand and shrink when its superview's width expands and shrinks. UIAutoresizingFlexibleHeight means the same thing for height. Adding these is equivalent to turning on "springs" in Interface Builder. I think that if there are no "struts" set, when the superview doubles in width, the view will double in width as well.
The strut settings are:
UIViewAutoresizingFlexibleRightMargin
UIViewAutoresizingFlexibleLeftMargin
UIViewAutoresizingFlexibleTopMargin
UIViewAutoresizingFlexibleBottomMargin
If UIViewAutoresizingFlexibleRightMargin is NOT set, the right strut is turned on. This means that the space between the right side of your view and the right side of its superview will not change when the view expands. The same concept holds for all the other margins.
There is as good discussion here: https://stackoverflow.com/a/10470469/472344.
I think you are confused by the self.view.bounds that you are getting. This is the bounds of the view before rotation, as jrturton pointed out the height is 1004 because 20 pixels are subtracted for the status bar. I don't know why after rotation the width and height haven't updated (I'm assuming your rotation happens before loading the view, otherwise the printed out bounds are exactly what you should expect), but I have noticed this as well after rotation. I have seen some discussion on Stackoverflow about this, but I can't find it at the moment.
My guess is that as jrturton said, when the view is loaded, it uses the bounds set in the xib (or the storyboard), and the resizing happens after viewDidLoad. Maybe try printing out the bounds of self.frame.view in viewDidAppear, as the bounds should have changed at that point.
To address the behaviour of the auto resizing mask settings again, understand that these do NOTHING unless the view's superview changes size. The auto resizing mask settings will tell your view how to automatically resize when its superview changes size. If you want your table view to have the same size as self.view always (such as after rotation, or zooming), you need to set it exactly as you have done:
table = [[UITableView alloc] initWithFrame:self.view.bounds];
table.autoresizingMask = UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleHeight;
[self.view addSubview:table];
1004 is correct - the status bar is 20px. Your first code snippet is fine.
Note that in viewDidLoad your view's orientation will still be in portrait - hence the numbers you are seeing. The resize happens afterwards. Using the bounds rectangle of the main view as the frame of a subview will always let the subview fill the whole view.

How to resize UIViews to fit within a smaller parent view

I have several view parameters saved in Core Data scaled to fit within self.view. When I fetch the view parameters I want to rescale the views to fit within a smaller parent view (newView). How do I get the viewSelected subviews to resize within the newView parent view?
newView = [[UIView alloc]initWithFrame:CGRectMake(100, 100, 200, 200)];
newView.autoresizesSubviews = YES;
for (Shape *shape in shapes) {
ShapeView *viewSelected = [[SquareView alloc]init];
[viewSelected setAutoresizingMask:UIViewAutoresizingFlexibleHeight |
UIViewAutoresizingFlexibleWidth];
viewSelected.frame = CGRectMake(shape.x,shape.y,shape.shapeSize,shape.shapeSize);
[newView addSubview:viewSelected];
}
[self.view addSubview:newView];
There are probably multiple ways of handling this, but I can think of two off of the top of my head.
Manually - What the autoresizing mask gives you is deterministic, so calculate it out.
Create an intermediate UIView that is the size of self.view, add your fetched views to it, and resize the intermediate view to the size of the "new smaller parent". Then your subviews will have resized appropriately. Then remove them from the intermediate view and add them to the desired destination view.

Resources