By now we should be used to the idea that all views in SwiftUI choose their own size, for example a Text view has the size required to render the string provided:

struct ContentView : View { var body: some View { Text("Nogitsune Takeshi") .font(.title) } }

Creates a view with the exact bounds necessary:

text view

We also showed that the .frame modifier actually creates a new view with the dimensions specifies, and positions the Text view within it, such that:

struct ContentView : View { var body: some View { Text("Nogitsune Takeshi") .font(.title) .frame(width: 200, height: 200) } }

Actually creates two views, a .frame that is 200×200 in size, and a Text within it with the exact bounds necessary to render its contents:

text inside frame

We looked into this process further in flexible frames, introduced the concept of layout neutral views that choose their own size based on their children, and showed that in either dimension .frame can have a fixed size, be layout neutral, or through minimum and maximum size constraints base its own size on that proposed by its own parent.

We’ll now take a look at another useful modifier view, one that adds padding around its child view, and has a number of different forms that we can use:

func padding(_ length: CGFloat) -> some View func padding(_ insets: EdgeInsets) -> some View func padding(_ edges: Edge.Set = .all, _ length: CGFloat? = nil) -> some View

The first form sets the padding of all edges to the length specified.

The second form sets the padding of each of the edges to the specific individual values you specify through the EdgeInsets value.

The third form sets the padding of the set of edges you specify to the length supplied, leaving other edges unpadded. The third form also allows you to specify nil as the length, instead of zero, which instructs SwiftUI to use a system default amount of padding appropriate for the situation.

Default values for all parameters of the third form are provided, which uses the system default padding for all edges, we’ll use that in our example:

struct ContentView : View { var body: some View { Text("Nogitsune Takeshi") .font(.title) .padding() } }

The .padding modifier is no different from modifiers like .frame, it doesn’t modify the Text in any way, it instead creates a new view that adds padding, and positions the Text view inside it as a child:

text with padding

The layout process is actually a little more interesting than just adding padding, and is almost but not quite layout neutral. It in fact works something like we see for stacks when considering spacing.

  1. Parent proposes a size to the padding view.
  2. Padding view subtracts the appropriate padding length from each edge.
  3. Padding view proposes this smaller size to its child.
  4. Child chooses its size.
  5. Padding view takes the child’s size, adds the appropriate padding length back to each edge, and chooses that as its own size.
  6. Parent positions the padding view within its bounds.

Thus a .padding view always tightly wraps its child (aside from the padding itself), with both being positioned by the parent frame, but at the same time adds an additional constraint (the padding) to the size the child can be.

We can demonstrate this by placing the .padding inside a frame:

struct ContentView : View { var body: some View { Text("Nogitsune Takeshi") .font(.title) .padding() .frame(width: 200, height: 200) } }

The .frame has a fixed size of 200×200, the .padding view subtracts the system default padding of 16px (in this case) from each side, and supplies the Text with a proposed size of 168×168.

That’s too small for Text to layout on one line, but still enough room to wrap over two lines, so it returns its size appropriately to do that. .padding adds back the padding before returning its size, and the .frame positions the padding view inside it.

text with padding inside frame

As we can see, the padding view still tightly wraps the Text, it isn’t increased in height or width to try and fill the parent frame, and is centered within it instead.