'In SwiftUI what is an easy way to align more than one view in ZStack

I have a view and I want to add two icons to it, at top right side and at bottom right side. I managed to do that:

image

I used two ZStacks:

ZStack(alignment: .bottomTrailing)
{
    ZStack(alignment: .topTrailing)
    {
        Image(item.thumbnailImage)
            .clipShape(Circle())
            .overlay(Circle()
                        .stroke(Color.gray, lineWidth: 2))
        
        if item.isFavorite
        {
            Image(systemName: "star.fill")
                .foregroundColor(.yellow)
                .offset(x: 7, y: -7)
        }
    }
    
    if item.ordered
    {
        Image(systemName: "checkmark.square.fill")
            .offset(x: 7, y: 7)
    }
}

But I have a feeling that there should be a simpler way than nesting ZStacks inside. Besides looks like the small icons don't have their x-centers aligned. I can probably fix that by changing an offset but that would make the code even more clumsy.

Is there a simpler way?



Solution 1:[1]

You can use the overlay modifier, like this:

import PlaygroundSupport
import SwiftUI

PlaygroundPage.current.setLiveView(
    Circle()
        .strokeBorder(Color.gray, lineWidth: 6)
        .frame(width: 44, height: 44)
        .overlay(
            Image(systemName: "star.fill")
                .foregroundColor(.yellow)
                .offset(x: 7, y: -7),
            alignment: .topTrailing)
        .overlay(
            Image(systemName: "checkmark.square.fill")
                .offset(x: 7, y: 7),
            alignment: .bottomTrailing)
        .padding()
)

a gray circle overlaid by a yellow star at the top right and also overlaid by a checkmark in a black square at the bottom right

If your deployment target is iOS 15 or later (or an aligned version of macOS, tvOS, or watchOS), you can use the ViewBuilder version of overlay instead:

import PlaygroundSupport
import SwiftUI

PlaygroundPage.current.setLiveView(
    Circle()
        .strokeBorder(Color.gray, lineWidth: 6)
        .frame(width: 44, height: 44)
        .overlay(alignment: .topTrailing) {
            Image(systemName: "star.fill")
                .foregroundColor(.yellow)
                .offset(x: 7, y: -7)
        }
        .overlay(alignment: .bottomTrailing) {
            Image(systemName: "checkmark.square.fill")
                .offset(x: 7, y: 7)
        }
        .padding()
)

We can use lorem's suggestion to align the symbol centers using a VStack. Then we can factor out the two offset modifiers into a padding on the VStack.

import PlaygroundSupport
import SwiftUI

PlaygroundPage.current.setLiveView(
    Circle()
        .strokeBorder(Color.gray, lineWidth: 6)
        .frame(width: 44, height: 44)
        .overlay(
            VStack(spacing: 0) {
                Image(systemName: "star.fill")
                    .foregroundColor(.yellow)

                Spacer()

                Image(systemName: "checkmark.square.fill")
            }.padding([.top, .bottom, .trailing], -7),
            alignment: .trailing)
        .padding()
)

same as prior image, but now the star and the checkmark are aligned horizontally

Solution 2:[2]

Use only one Zstack and wrap the two icons in a VStack.

        ZStack(alignment: .trailing) {
            Circle()
             .stroke(Color.gray, lineWidth: 2)

            VStack {
                Image(systemName: "star.fill")
                Spacer()
                Image(systemName: "checkmark.square.fill")
            }
            // Adjust the position of star and checkmark
            .offset(x: -10)
        }
        .frame(width: 100, height: 100)

enter image description here

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2