live activity: fix vertical centering on lock screen

GeometryReader as the layout root pinned content top-left, and forcing
maxHeight:.infinity over-claimed the whole proposed height, centring the
content below true vertical centre (bigger gap above) on the Lock Screen.

Demote GeometryReader to a background side-channel that only measures the
slot width for the rich-vs-compact branch; the content stays intrinsic-sized
so the system vertically centres it itself.
This commit is contained in:
FuelBoard Contributor
2026-08-20 10:16:21 +01:00
parent d8412c132a
commit 28bb245c93
@@ -67,32 +67,46 @@ private struct FuelBoardLiveActivityView: View {
/// Below this ACTUAL proposed width we show the compact strip (CarPlay
/// small / Watch smart stack); at/above it we show the full card. The
/// decision is made from the real space the system hands the body via
/// GeometryReader, NOT from ViewThatFits ideal-width measurement the
/// latter is broken for truncating text (a long station name inflates the
/// "ideal" width past the iPhone Lock Screen and collapses the full card).
/// decision is made from the real width the system hands the body, read
/// via a background GeometryReader NOT ViewThatFits ideal-width
/// measurement (that's broken for truncating text: a long station name
/// inflated the "ideal" width past the iPhone Lock Screen and collapsed
/// the full card).
private let compactWidthThreshold: CGFloat = 280
/// Measured slot width (drives the rich-vs-compact branch). Measured in a
/// background GeometryReader so it does NOT act as the layout container:
/// a GeometryReader root pins content top-left, and forcing a
/// maxHeight:.infinity frame on it over-claims the whole proposed height,
/// centring the content below true vertical centre (bigger gap above) on
/// the Lock Screen. Measuring behind the scenes keeps the content
/// intrinsic-sized so the system vertically centres it itself.
@State private var slotWidth: CGFloat = 400
var body: some View {
Link(destination: context.state.mapsURL) {
GeometryReader { geo in
Group {
// Branch on the ACTUAL proposed width. iPhone/iPad offer the
// full Lock Screen width (>= threshold) rich card, no matter
// how long the station name is. Truly small slots (CarPlay /
// Watch) offer much less compact strip.
Group {
if geo.size.width >= compactWidthThreshold {
if slotWidth >= compactWidthThreshold {
richBody
} else {
compactBody
}
}
// A GeometryReader hands its content the FULL proposed frame
// (content is otherwise pinned top-left), so centre the card
// within that space keeps the Lock Screen content vertically
// aligned instead of sitting high.
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
// Fill the card width so the background measure reads the real
// slot, not the intrinsic content width.
.frame(maxWidth: .infinity)
// Side-channel width measurement never the layout container.
.background(
GeometryReader { geo in
Color.clear
.onAppear { slotWidth = geo.size.width }
.onChange(of: geo.size.width) { _, w in slotWidth = w }
}
)
}
}