Working with frames instead of autolayout

Hi everyone,


so I haven't understood one thing:

Basically, I don't use the storyboard to create the user interface since for complex UIs like having multiple subviews in one container view etc. using the storyboard is a pain.


I am using the good old way with the frames. First I created a template for the iPhone 8 screen using Adobe CC, then I started coding the UI in Xcode by having a look at the frames. For example, I am creating a label like this:


  ..
  let screenSize = UIScreen.main.bounds
  let width = screenSize.width
  let height = screenSize.height


  let accountTypeLabel = UILabel()
  accountTypeLabel.frame = CGRect(x: width * (53 / width), y: height * (172 / height), width: width * (269 / width), height: height * (37 / height))
  accountTypeLabel.text = ACCOUNT_TYPE_LABEL
  accountTypeLabel.textColor = UIColor.white
  accountTypeLabel.backgroundColor = DARK_BLUE_COLOR
  accountTypeLabel.textAlignment = .center
  let accountTypeLabelLabelHeight = accountTypeLabel.frame.size.height
  accountTypeLabel.font = UIFont(name: "Kefa", size: accountTypeLabelLabelHeight * (22 / accountTypeLabelLabelHeight))
  ..

Depending on the iPhone device, we have different widths and heights of course. So I just put them into variables and define width and height of my label relatively to the screensize of the corresponding iPhone.

Well, this works great for iPhone 8. But I just tried to run the code for the iPhone 8 Plus and here, the label is a little bit more on the left part of the screen, so it doesn't look the same as for iPhone 8.


Now my question, why is that the case? Since I am putting the height and width always relatively to the screensize, I would have expected to have some kind of an "autolayout" functionality


Can anyone help?


Thanks!

Replies

You are, more or less, essentially multiplying and dividing by the same value ( e.g., width * (53 / width) ). The actual result will probably be about 53 +/- 1 regardless of the width. So, you are essentially putting the lower corner of the frame at the same offset in the view, regardless of the size of the screen. You need to take the width, subtract the width of the frame, divide by 2, and use that as the x-value of your offset, if you want to center it horizontally.


You are giving explicit screen coordinates on where to place the frame, essentially (53,172) because multiplication and division by the same quantity yields about 1. So, UIKit is putting it where you told it to. Doesn't adjust based on screen size. You have to adjust for screen size, and your math isn't doing that.


Autolayout can be done manually, rather than through IB, but, that's a whole different topic.

You are absolutely correct.


In case of iPhone 8 screen, the width is 375. Width for iPhone 11 is 414

I actually don't put the frame correctly to the screen size.


It would have been correct, if I made it like this:

53/375 = 0.1413


And then used in the frame like this..

... CGRect(x: width * 0.1413) ..

because then, it is set relatively to the screen even when using other device.


So what would be the easiest way to change it being adjustable to screen size? I mean working with something like

width * 0.1413 is not really easyy to read and understand.

Maybe that comes from line 12.


You center the label in its container.

If the container is larger for any reason, label will move.


However, I really dont understand what you do:

width * (53 / width)

width is Double, hence this is exactly 53 (at the precision of double operation.


Did I miss something ?

I guess, I know what to do:
I need to define two variables like:

// SCREEN SIZE FOR IPHONE 8
let IPHONE8_SCREEN_WIDTH:CGFloat = 375.0

let IPHONE8_SCREEN_HEIGHT:CGFloat = 667.0


and then change my code above to:

let accountTypeLabel = UILabel()
 accountTypeLabel.frame = CGRect(x: width * (53 / IPHONE8_SCREEN_WIDTH), y: height * (172 / IPHONE8_SCREEN_HEIGHT), width: width * (269 / IPHONE8_SCREEN_WIDTH), height: height * (37 / IPHONE8_SCREEN_HEIGHT))
 accountTypeLabel.text = ACCOUNT_TYPE_LABEL
 accountTypeLabel.textColor = UIColor.white
accountTypeLabel.backgroundColor = DARK_BLUE_COLOR
 accountTypeLabel.textAlignment = .center
 let accountTypeLabelLabelHeight = accountTypeLabel.frame.size.height
  accountTypeLabel.font = UIFont(name: "Kefa", size: accountTypeLabelLabelHeight * (22 / accountTypeLabelLabelHeight))
       

This works for any devices then, right?

Hi Claude,


I thought, I knew how to deal with frames but it turned out, I didn't. So now, I might have found a working solution to have a functionality like autolayout but with frames.

What I learned by experience:

- Autolayout can do a lot of things, much simpler that in code.

- however, some constraints are hard to express or impossible, and in some cases you need to adapt constraints (for example to screen size).

For those limited cases, I create IBOutlet to the basic constraint and modify it in code.


I found it a pretty good compromise.


Anyway, thanks for the feedback, good continuation and don't forget to close the thread.

You can do it just like autolayout. You can always find the center and work out from there like (Width/2 - 100) to place something that is 200 wide in the center. You can size things as a fraction of the Width using Width/20. You can add the left over space between all things by adding up the width of all things, subtracting that from Width and dividing that by the number of free spaces - then distributing that between the different things - sometimes allocating two or more space units. It's actually lots of fun. It also will allow for rotations.


And an easier approach, much less elegant, is to define a container view with a fixed size and frame that in the center of the screen. Then add everything to that fixed size container.