I have a complicated alignment situation, which I was hoping there was some solution with alignmentGuide
s, but I can't figure it out.
I need to align the following list of entries (in a mono font), such that as a group it is horizontally centered.
But also that it is positioned the same way, and aligned the same way along the leading edge, even if one of the entries in a row is longer:
I don't want to hardcode any sizing or positioning values.
Here's some simple code to replicate:
struct TestView: View {
let values: [(String, String)] = [
("03", "30.123"),
("02", "33.222"),
("01", "completed")
]
var body: some View {
LazyVStack {
ForEach(values, id: \.1) { tuple in
HStack(spacing: 24) {
Text(tuple.0)
Text(tuple.1)
}
}
}
.frame(width: 350, height: 250) // to simulate outer container dimensions
}
}
CodePudding user response:
Here is a simple way may it help:
struct ContentView: View {
var body: some View {
TestView()
}
}
struct TestView: View {
let values: [(String, String)] = [
("03", "30.123"),
("02", "33.222"),
("01", "completed")
]
var body: some View {
HStack(spacing: 24) {
VStack { ForEach(values, id: \.1) { tuple in Text(tuple.0) } }
VStack(alignment: .leading) { ForEach(values, id: \.1) { tuple in
Text("00.000")
.foregroundColor(Color.clear)
.overlay(Text(tuple.1).lineLimit(1).fixedSize(), alignment: .leading)
} }
}
.font(Font.system(.body, design: .monospaced))
.padding(5.0)
.background(Color.pink.opacity(0.5).cornerRadius(5.0))
}
}
CodePudding user response:
I found a way to solve this, though it seems quite convoluted to me. Posting here, but still would love a better solution.
The idea was to use a placeholder with dummy content (relying on the fact that this was a monospaced font) and using anchorPreference
to align around its leading edge.
struct LeadingPreferenceKey: PreferenceKey {
static var defaultValue: Anchor<CGPoint>? = nil
static func reduce(value: inout Anchor<CGPoint>?,
nextValue: () -> Anchor<CGPoint>?) {
value = nextValue()
}
}
Then, set that in anchorPreference
, and capture it in overlayPreferenceValue
and position with offset(x:)
LazyVStack(alignment: .center) {
row(("00", "00.000")) // placeholder that's center-positioned
.opacity(0)
.anchorPreference(key: LeadingPreferenceKey.self,
value: .leading, , transform: { $0 })
}
.overlayPreferenceValue(LeadingPreferenceKey.self) { leading in
GeometryReader { geo in
LazyVStack(alignment: .leading) {
ForEach(values, id: \.1) { tuple in
row(tuple)
}
}
.offset(x: leading.map { geo[$0].x } ?? 0)
}
}
func row(_ tuple: (String, String)) -> some View {
HStack(spacing: 24) {
Text(tuple.0)
Text(tuple.1)
}
}
CodePudding user response:
Here is a solution that will provide your alignment as well as control of the width of your columns. It is a separate view that takes a tuple and returns two VStacks in an HStack contained by width:
struct TwoItemLeadingAlignedColumn: View {
let firstColumnItems: [String]
let secondColumnItems: [String]
let width: CGFloat?
init(items: [(String, String)], width: CGFloat? = nil) {
firstColumnItems = items.map { $0.0 }
secondColumnItems = items.map { $0.1 }
self.width = width
}
var body: some View {
GeometryReader { geometry in
HStack {
VStack(alignment: .center) {
ForEach(firstColumnItems, id: \.self) {item in
Text(item)
}
}
Spacer()
.frame(width: geometry.size.width * 0.25)
VStack(alignment: .leading) {
ForEach(secondColumnItems, id: \.self) {item in
Text(item)
}
}
Spacer()
}
.frame(width: width)
}
}
}
struct TestView: View {
let values: [(String, String)] = [
("03", "30.123"),
("02", "33.222"),
("01", "completed")
]
var body: some View {
TwoItemLeadingAlignedColumn(items: values, width: 150)
.frame(width: 350, height: 250) // to simulate outer container dimensions
}
}
This answer will work whether or not you are using a monospaced font.
Update: Center first column