1. SwiftUI Layout Introduction

In this technology project, we will explore how SwiftUI handles layout. Some things have already been explained, some you probably figured out on your own, but more are things you take for granted at this point, so I hope a detailed exploration will really shed some light on the way SwiftUI works.

Along the way, you’ll also learn how to create more advanced layout alignment, build special effects using GeometryReader, and much more — some of the really powerful features I know you’ll love to deploy in your own applications.

Go ahead and create a new iOS project using the single-view application template and name it layoutDageMetricy. You need to provide an image in the resources directory to follow the chapter on custom alignment guidelines, but it can be anything you want — it’s really just a placeholder.

2. Working principle of SwiftUI layout

All SwiftUI layouts have three simple steps and understanding these steps is the key to getting a great layout every time. The steps are as follows:

  1. The parent view provides a size and asks its children about the size.

  2. The child view chooses its own size based on its own information, and the parent view must respect this choice.

  3. The superview then locates the child views in its coordinate space.

Behind the scenes, SwiftUI performs the fourth step: although it stores the position and size as floating-point numbers, when rendering, SwiftUI rounds all pixels to the nearest value so that our graphics are still clear.

These three rules seem simple enough, but they allow us to create very complex layouts where each view can decide how and when to resize without parental involvement.

To demonstrate these rules in action, I want you to modify the default SwiftUI template to add the background() modifier, as follows:

struct ContentView: View { var body: some View { Text("Hello, World!" ) .background(Color.red) }}Copy the code

You’ll see that the background color is tightly wrapped around the text itself — it just takes up enough space to accommodate the content we’re providing.

Hello, world

Now, think about this: How big is a ContentView? As you can see, the body of the ContentView (what it renders) is some text with a background color. So the ContentView is always the same size as its body, no more, no less. This is called layout neutral: The ContentView itself does not have any size, but can be adjusted to fit any size as needed.

Why is the SwiftUI modifier order important in Project3? I explained that when you apply modifiers to a view, we actually get a new view type called ModifiedContent, which stores the original view and its modifiers. This means that when we apply the modifier, the actual view that enters the hierarchy is the modified view, not the original view.

In our simple background() example, this means that the top-level view in the ContentView is the background and the interior is the text. The background, like the ContentView, is layout neutral, so it only passes layout information as needed — you can end up with a series of layout information until you get to a definitive answer.

If we put this into a three-step layout system, we end up with a conversation like this:

  • SwiftUI: “Hey ContentView, you own the whole screen — how much do you need? “(Superview asks size)

  • ContentView: “I don’t care; I’m layout neutral. Let me ask my kids: Hey, background, you can use the whole screen — how much do you need? “(Parent view asks size)

  • Background: “I don’t care; My layout is also neutral. Let me ask my kids: Hey Text, you can keep the whole screen to yourself — how much do you need? “(Superview asks size)

  • Text: “Well, my Text is the default font for ‘Hello, World’, so I need X pixels wide and Y pixels high. I don’t need the whole screen, just this one.” (The child chooses its size.)

  • Background: “Got it. Hey, ContentView: I need X * Y pixels.”

  • ContentView: “understanding. Hey SwiftUI: I need X * Y pixels.”

  • SwiftUI: “Ok. Well, that leaves a lot of room, so I’ll put your size in the middle.” (The superview locates its children in its coordinate space.)

So, when we say TText(“Hello, World! Background (color.red)), and the text view becomes a child view of its background. SwiftUI works effectively from the bottom up when it comes to views and their modifiers.

Now consider this layout:

Text("Hello, World!" ) .padding(20) .background(Color.red)Copy the code

This time the conversation is more complicated: the padding() no longer provides all the space for its children, because it needs to subtract 20 points from each side to make sure it has enough space to fill in. Then, when the answer comes back from the text view, the padding() adds 20 dots on each side as requested to fill it in.

So, more like this:

  • SwiftUI: ContentView, you can have the whole screen, how much do you need?

  • ContentView: Background, you can have the whole screen, how much do you need?

  • Background: Fill, you can have the whole screen, how much do you need?

  • Fill: text, you can have the size of the entire screen minus 20 points on each side, how much do you need?

  • Text: I need X * Y.

  • Fill: I need X * Y plus 20 points on each side.

  • Background: I need X * Y plus 20 points on each side.

  • ContentView: I need X * Y plus 20 points on each side.

  • SwiftUI: OK, I’ll put you in the middle.

Ps. IOS development and communication technology: welcome to join, no matter you are big or small white welcome to enter, share BAT, Ali interview questions, interview experience, discuss technology, we exchange learning and growth together

Hello, world

If you remember why the SwiftUI modifier order is important? . In other words, this code:

Text("Hello, World!" ) .padding() .background(Color.red)Copy the code

And this:

Text("Hello, World!" ) .background(Color.red) .padding()Copy the code

Produces two different results. Hopefully now you can understand why: Background () is layout-independent, so it determines how much space is needed by asking the child objects and using the same values. If the child of background() is a text view, the background will fit the text perfectly, but if the child is a padding(), it will receive the adjusted value back, including the padding amount.

Click to obtain Swift information

These layout rules have two interesting side effects.

First, if the view hierarchy is completely layout neutral, it will automatically take up all available space. For example, shapes and colors are layout independent, so if the view contains colors and nothing else, it will automatically fill the screen, as shown below:

var body: some View {    Color.red}
Copy the code

Remember that color.red is a view by itself, but because it is layout neutral, it can be drawn at any size. When we use it in background(), the simplified layout dialog works like this:

  • Background: Hey, text, you can have the whole screen, how much do you want?

  • Text: I need X by Y points; I don’t need the rest.

  • Background: Ok. Hey, red, you can have X times Y, what do you want?

  • Red: I don’t care; My layout is neutral, so X by Y points sounds good.

The second interesting side effect is the one we encountered earlier: if we use frame() on an image that can’t be resized, we get a larger frame without changing the size inside the image. This might have been confusing before, but once you think of Frame as the parent object of the image, it makes perfect sense:

  • ContentView provides the entire screen.

  • Frame reports that it wants 300×300.

  • Frame then asks the inside image what size it wants.

  • Non-resizable images return a fixed size such as 64×64.

  • Frame then positions the image in the center of itself.

When you listen to Apple’s SwiftUI engineers talk about modifiers, you’ll hear them refer to them as views — “The Frame View,” “The Background View,” and so on. I think this is a good mental model to understand exactly what’s going on: apply modifiers to create new views, not just modify existing ones.