How to align multiple objects horizontally among different Stacks

Viewed 159

I am illustrating the problem I have with a simple example below. It all boils down to properly aligning VStacks with text to a Circle. This is the image of what I am trying to get at. Is there any way to align things properly without using hardcoded paddings?

enter image description here

This is the code producing the left image

struct MyAlignedView: View {
    var body: some View {
        HStack {
            VStack(alignment: .center, spacing: 10) {
                Circle()
                    .frame(width: 20, height: 20)
                Text("|")
                Circle()
                    .frame(width: 20, height: 20)
                Text("|")
                Circle()
                    .frame(width: 20, height: 20)
            }
            
            VStack {
                VStack{
                    Text("stack 1")
                }
                
                VStack{
                    Text("stack 2")
                    Text("text2")
                    Text("more text")
                }
                
                VStack{
                    Text("stack 3")
                    Text("text3")
                }
            }
        }
    }
}
1 Answers

Here's one way to do it. I put the Circle and the corresponding text into an HStack to keep them aligned. I let every other Circle manage the lines. That keeps them vertically aligned with the Circles.

If you were to continue this, the next Circle would have two lines, or a line and a space if it is the last one.

struct ContentView: View {
    var body: some View {
        HStack {
            VStack(alignment: .center, spacing: 0) {
                HStack {
                    Circle()
                        .frame(width: 20, height: 20)
                    Text("stack 1")
                        .frame(width: 80, height: 40)
                }
                HStack {
                    VStack {
                        Text("|")
                        Circle()
                            .frame(width: 20, height: 20)
                        Text("|")
                    }
                    VStack {
                        Text("stack 2")
                        Text("text2")
                        Text("more text")
                    }
                    .frame(width: 80, height: 40)
                }
                HStack {
                    Circle()
                        .frame(width: 20, height: 20)
                    VStack {
                        Text("stack 3")
                        Text("text3")
                    }
                    .frame(width: 80, height: 40)
                }
            }
        }
    }
}

Demo running in simulator


There is a lot of redundancy that needs to be managed. This can be put into a loop that can automatically figure out which lines to add and/or hide:

struct TextLines {
    let lines: [String]
}

struct BulletPoints: View {
    let textLines: [TextLines]
        
    var body: some View {
        HStack {
            VStack(alignment: .center, spacing: 0) {
                ForEach(0 ..< textLines.count) { idx in
                    HStack {
                        VStack {
                            if !idx.isMultiple(of: 2) {
                                Text("|")
                            }
                            Circle()
                                .frame(width: 20, height: 20)
                            if !idx.isMultiple(of: 2) {
                                Text("|").opacity(idx == self.textLines.count - 1 ? 0 : 1)
                            }
                        }
                        VStack {
                            ForEach(self.textLines[idx].lines, id: \.self) { line in
                                Text(line)
                            }
                        }
                        .frame(width: 80, height: 40)
                    }
                }
            }
        }
    }
}

struct ContentView: View {
    var body: some View {
        BulletPoints(textLines: [
           .init(lines: ["stack1"]),
           .init(lines: ["stack 2", "text2", "more text"]),
           .init(lines: ["stack 3", "text3"]),
           .init(lines: ["stack 4"])
       ])
    }
}

Demo showing 4 lines of bulleted text

Related