Align views in Picker

Viewed 268

How do I align the Color views in a straight line with the text to the side?

To look like so (text aligned leading):

█  red
█  green
█  blue

Or this (text aligned center):

█    red
█  green
█   blue

Current code:

struct ContentView: View {
    @State private var colorName: Colors = .red
    
    var body: some View {
        Picker("Select color", selection: $colorName) {
            ForEach(Colors.allCases) { color in
                HStack {
                    color.asColor.aspectRatio(contentMode: .fit)
                    
                    Text(color.rawValue)
                }
            }
        }
    }
}


enum Colors: String, CaseIterable, Identifiable {
    case red
    case green
    case blue
    
    var id: String { rawValue }
    var asColor: Color {
        switch self {
        case .red:      return .red
        case .green:    return .green
        case .blue:     return .blue
        }
    }
}

Result (not aligned properly):

Result

Without the Picker, I found it is possible to use alignmentGuide(_:computeValue:) to achieve the result. However, this needs to be in a Picker.

Attempt:

VStack(alignment: .custom) {
    ForEach(Colors.allCases) { color in
        HStack {
            color.asColor.aspectRatio(contentMode: .fit)
                .alignmentGuide(.custom) { d in
                    d[.leading]
                }
            
            Text(color.rawValue)
                .frame(maxWidth: .infinity)
                .fixedSize()
        }
    }
    .frame(height: 50)
}

/* ... */

extension HorizontalAlignment {
    struct CustomAlignment: AlignmentID {
        static func defaultValue(in context: ViewDimensions) -> CGFloat {
            return context[HorizontalAlignment.leading]
        }
    }
    
    static let custom = HorizontalAlignment(CustomAlignment.self)
}

Result of attempt:

Result 2

2 Answers

Possible solution is to use dynamic width for labels applied by max calculated one using view preferences.

Here is a demo. Tested with Xcode 13beta / iOS15

demo

Note: the ViewWidthKey is taken from my other answer https://stackoverflow.com/a/63253241/12299030

struct ContentView: View {
    @State private var colorName: Colors = .red
    @State private var maxWidth = CGFloat.zero

    var body: some View {
        Picker("Select color", selection: $colorName) {
            ForEach(Colors.allCases) { color in
                HStack {
                    color.asColor.aspectRatio(contentMode: .fit)
                    Text(color.rawValue)
                }
                .background(GeometryReader {
                    Color.clear.preference(key: ViewWidthKey.self, 
                        value: $0.frame(in: .local).size.width)
                })
                .onPreferenceChange(ViewWidthKey.self) {
                    self.maxWidth = max($0, maxWidth)
                }
                .frame(minWidth: maxWidth, alignment: .leading)
            }
        }
    }
}

I think this should align your text and fix your issue.

struct ContentView: View {
    @State private var colorName: Colors = .red
    
    var body: some View {
        Picker("Select color", selection: $colorName) {
            ForEach(Colors.allCases) { color in
                HStack() {
                    color.asColor.aspectRatio(contentMode: .fit)
                    
                    Text(color.rawValue)
                        .frame(width: 100, height: 30, alignment: .leading)
                }
            }
        }
    }
}

A simple .frame modifier will fix these issues, try to frame your text together or use a Label if you don't want to complicate things when it comes to pickers or list views.

If you do go forward with this solution try to experiment with the width and height based on your requirements, and see if you want .leading, or .trailing in the alignment

Related