How to change vertical alignment of individual items in SwiftUI HStack?

1 week ago 15
ARTICLE AD BOX

Here are some possible ways to fix.

1. Apply maxHeight: .infinity to the Button and .fixedSize to the HStack

A .frame modifier can be applied to the Button with maxHeight: .infinity and alignment: .bottom. Then, .fixedSize(horizontal: false, vertical: true) is aplied to the HStack. This stops the button from consuming any more space than it actually needs to.

HStack(spacing: 8) { // default alignment TextField("Chat", text: $textInput, axis: .vertical) // ... Button(action: send) { // ... } .frame(maxHeight: .infinity, alignment: .bottom) } .fixedSize(horizontal: false, vertical: true) // + padding and other modifiers

This is perhaps the simplest solution in this particular case. However, the modifier .fixedSize can sometimes have undesirable side effects, especially when text is concerned. So this technique may not be suitable in other cases.


2. Use an .alignmentGuide

For an alignment guide to work, you need to know the height of either the text or the button. Since the height of the text is variable, this technique is probably only useful if the height of the button is known or can be guessed. The alignment guide is then applied to the other element in the HStack, this being the text.

If you have to guess the size of the button then you might like to give it a .frame with minWidth and minHeight, before adding the background. Apple's guidelines for buttons recommend a minimum size of 44 points.

Note that the alignment used for the .alignmentGuide must match the alignment used for the HStack. Since the alignment guide is being applied to the text and not the button, .bottom should be used. This then takes effect for elements inside the HStack that don't have their own alignment guide (in other words, the button):

private let minButtonSize: CGFloat = 44 HStack(alignment: .bottom, spacing: 8) { TextField("Chat", text: $textInput, axis: .vertical) // + previous modifiers .alignmentGuide(.bottom) { dim in dim.height + max(0, (minButtonSize - dim.height) / 2) } Button(action: send) { Image(systemName: "paperplane.fill") // ... .frame(minWidth: minButtonSize, minHeight: minButtonSize) .background(Color(.label), in: .circle) } } // + padding and other modifiers

3. Use a placeholder to reserve space, then show the button as an overlay

If you know the size of the button (re. point 2), you can also use a placeholder to reserve space for it. Then show the actual button as an overlay over the HStack, with alignment .bottomTrailing.

HStack(spacing: 8) { // default alignment TextField("Chat", text: $textInput, axis: .vertical) // ... // Placeholder Color.clear .frame(width: minButtonSize, height: minButtonSize) } .overlay(alignment: .bottomTrailing) { Button(action: send) { // ... .frame(minWidth: minButtonSize, minHeight: minButtonSize) .background(Color(.label), in: .circle) } } // + padding and other modifiers

Alternatively, a copy of the Button can be used as the placeholder. This works too and it's a good way to get the size right, but you need to take a few extra measures:

be sure to apply .hidden() and .accessibilityHidden(true) to the copy, to keep it hidden apply .disabled(true) too, to make quite sure it cannot receive focus.

4. Use .matchedGeometryEffect to determine the position for the button

Another way to solve is to use .matchedGeometryEffect. This technique works when you don't know the size of the button and don't want to guess it.

The source for the matched geometry can be the HStack itself, before padding is applied. The button is matched to the position of the source using anchor: .bottomTrailing. @Namespace private var ns HStack(spacing: 8) { // default alignment TextField("Chat", text: $textInput, axis: .vertical) // ... Button(action: send) { // ... } .matchedGeometryEffect( id: 0, in: ns, properties: .position, anchor: .bottomTrailing, isSource: false ) } .matchedGeometryEffect( id: 0, in: ns, anchor: .bottomTrailing, isSource: true ) // + padding and other modifiers

All techniques give a similar result:

Screenshot

Screenshot

Read Entire Article