Search code examples
iosswiftswiftuitext-alignment

How to vertically align top line of multiline Text and Button title with Toggle label, in Hstack?


I have the following code:

struct ContentView: View {
    @State var isOn: Bool = false

    var body: some View {
        HStack(alignment: .top) {
            Toggle(isOn: $isOn) {
                Text("Hello")
            }

            Text("The quick brown fox jumped over the lazy dog")

            Spacer()

            Button("Go") {
                print("Knock knock.")
            }
            .padding()
        }
        .padding()
    }
}

Which results in:

Result

How do I vertically align the top line: The quick brown fox and Go with desiredly positioned Hello?

Of course I can manually tune the alignment with hard-coded .offset(); I have this now in my code. But that's ugly and will fall apart if I'd change the font size(s) for example. I'm looking for a proper method without hard-code values.

Isn't this possible with SwiftUI alignment features?


Solution

  • Here is one approach (the Toggle button toggles background colors on/off):

    import SwiftUI
    
    struct ContentView: View {
        
        @State var isOn: Bool = false
    
        @State var c1: Color = Color(red: 1.00, green: 0.75, blue: 0.75)
        @State var c2: Color = Color(red: 0.75, green: 1.00, blue: 0.75)
        @State var c3: Color = Color(red: 1.00, green: 1.00, blue: 0.00)
        @State var c4: Color = Color(red: 0.80, green: 1.00, blue: 0.75)
        @State var c5: Color = Color(red: 0.75, green: 0.75, blue: 0.25)
        
        var body: some View {
            
            HStack(alignment: .top) {
                    
                Spacer()
                
                Toggle(isOn: $isOn) {
                    Text("Hello")
                        .background(isOn ? c1 : .clear)
                }
                .background(isOn ? c2 : .clear)
                .offset(CGSize(width: 0.0, height: -5.0))
                
                Text("The quick brown fox jumped over the lazy dog")
                    .background(isOn ? c3 : .clear)
                    
                Spacer()
                
                Button("Go") {
                    print("Knock knock.")
                }
                .background(isOn ? c4 : .clear)
                
                Spacer()
                
            }
            .background(isOn ? c5 : .clear)
            .padding()
            
        }
    
    }
    

    Output (red line is added after, to show the text baselines):

    enter image description here

    You would probably want to use GeometryReader to calculate the correct offset, rather than using the hard-coded y: -5.0


    Edit -- after quick searching for dynamic sizing instead of hard-coded y: -5.0

    Based on info found here: https://stackoverflow.com/a/63050004/6257435

    Looks like custom .alignmentGuide is what you need:

    import SwiftUI
    
    extension VerticalAlignment {
        private enum XAlignment : AlignmentID {
            static func defaultValue(in d: ViewDimensions) -> CGFloat {
                return d[VerticalAlignment.top]
            }
        }
        static let xAlignment = VerticalAlignment(XAlignment.self)
    }
    
    struct ContentView: View {
        
        @State var isOn: Bool = true
    
        @State var c1: Color = Color(red: 1.00, green: 0.75, blue: 0.75)
        @State var c2: Color = Color(red: 0.75, green: 1.00, blue: 0.75)
        @State var c3: Color = Color(red: 1.00, green: 1.00, blue: 0.00)
        @State var c4: Color = Color(red: 0.80, green: 1.00, blue: 0.75)
        @State var c5: Color = Color(red: 0.75, green: 0.75, blue: 0.25)
        
        var body: some View {
            
            HStack(alignment: .xAlignment) {
    
                Spacer()
                
                Toggle(isOn: $isOn) {
                    Text("Hello")
                        .background(isOn ? c1 : .clear)
                }
                    .background(isOn ? c2 : .clear)
                    .alignmentGuide(.xAlignment) { $0.height * 0.5 }
                
                Text("The quick brown fox jumped over the lazy dog")
                    .background(isOn ? c3 : .clear)
                    .alignmentGuide(.xAlignment) {
                        ($0.height - ($0[.lastTextBaseline] - $0[.firstTextBaseline])) * 0.5
                    }
    
                Spacer()
                
                Button("Go") {
                    print("Knock knock.")
                }
                .background(isOn ? c4 : .clear)
                .alignmentGuide(.xAlignment) { $0.height * 0.5 }
    
                Spacer()
    
            }
            .background(isOn ? c5 : .clear)
            .padding()
    
        }
    
    }