From 4172ff3516993e0e96a3264227e2f0701311b368 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bj=C3=B6rkert?= Date: Fri, 6 Nov 2026 21:58:26 +0100 Subject: [PATCH] Show the display name in the small Live Activity With Show Display Name on, the small Live Activity used by CarPlay and the watch Smart Stack shows the app's display name at the top of its right column. Where space is short the slot label gives way first. Default LoopFollow_ names truncate at the head so the suffix stays visible. --- .../LoopFollowLiveActivity.swift | 92 ++++++++++--------- 1 file changed, 51 insertions(+), 41 deletions(-) diff --git a/LoopFollowLAExtension/LoopFollowLiveActivity.swift b/LoopFollowLAExtension/LoopFollowLiveActivity.swift index a9018ca94..c45a193ce 100644 --- a/LoopFollowLAExtension/LoopFollowLiveActivity.swift +++ b/LoopFollowLAExtension/LoopFollowLiveActivity.swift @@ -102,8 +102,33 @@ private struct SmallFamilyView: View { return snapshot.unit.displayName } + private func rightColumn(name: String?, slot: LiveActivitySlotOption, showsLabel: Bool, unitLabel: String?) -> some View { + VStack(alignment: .trailing, spacing: 2) { + if let name { + DisplayNameText(name: name) + } + if showsLabel { + Text(slot.gridLabel) + .font(.system(size: 12, weight: .semibold, design: .rounded)) + .foregroundStyle(.white.opacity(0.65)) + } + Text(slotFormattedValue(option: slot, snapshot: snapshot)) + .font(.system(size: 20, weight: .bold, design: .rounded)) + .monospacedDigit() + .foregroundStyle(.white) + .lineLimit(1) + .minimumScaleFactor(0.8) + if let unitLabel { + Text(unitLabel) + .font(.system(size: 11, weight: .regular, design: .rounded)) + .foregroundStyle(.white.opacity(0.55)) + } + } + } + var body: some View { let rightSlot = LAAppGroupSettings.smallWidgetSlot() + let name = LAAppGroupSettings.showDisplayName() ? LAAppGroupSettings.displayName() : nil HStack(alignment: .center, spacing: 0) { VStack(alignment: .leading, spacing: 2) { @@ -128,50 +153,20 @@ private struct SmallFamilyView: View { Spacer() if rightSlot != .none { - if let unitLabel = rightSlotUnitLabel(for: rightSlot) { - // Use ViewThatFits so the unit label appears on surfaces with - // enough vertical space (CarPlay) and is omitted where it doesn't - // fit (Watch Smart Stack). - ViewThatFits(in: .vertical) { - VStack(alignment: .trailing, spacing: 2) { - Text(rightSlot.gridLabel) - .font(.system(size: 12, weight: .semibold, design: .rounded)) - .foregroundStyle(.white.opacity(0.65)) - Text(slotFormattedValue(option: rightSlot, snapshot: snapshot)) - .font(.system(size: 20, weight: .bold, design: .rounded)) - .monospacedDigit() - .foregroundStyle(.white) - .lineLimit(1) - .minimumScaleFactor(0.8) - Text(unitLabel) - .font(.system(size: 11, weight: .regular, design: .rounded)) - .foregroundStyle(.white.opacity(0.55)) - } - VStack(alignment: .trailing, spacing: 2) { - Text(rightSlot.gridLabel) - .font(.system(size: 12, weight: .semibold, design: .rounded)) - .foregroundStyle(.white.opacity(0.65)) - Text(slotFormattedValue(option: rightSlot, snapshot: snapshot)) - .font(.system(size: 20, weight: .bold, design: .rounded)) - .monospacedDigit() - .foregroundStyle(.white) - .lineLimit(1) - .minimumScaleFactor(0.8) - } + // The first candidate that fits is shown: CarPlay has room for the + // unit label, the Watch Smart Stack drops it, and with a display name + // the slot label goes before the name does. + ViewThatFits(in: .vertical) { + if let unitLabel = rightSlotUnitLabel(for: rightSlot) { + rightColumn(name: name, slot: rightSlot, showsLabel: true, unitLabel: unitLabel) } - } else { - VStack(alignment: .trailing, spacing: 2) { - Text(rightSlot.gridLabel) - .font(.system(size: 12, weight: .semibold, design: .rounded)) - .foregroundStyle(.white.opacity(0.65)) - Text(slotFormattedValue(option: rightSlot, snapshot: snapshot)) - .font(.system(size: 20, weight: .bold, design: .rounded)) - .monospacedDigit() - .foregroundStyle(.white) - .lineLimit(1) - .minimumScaleFactor(0.8) + rightColumn(name: name, slot: rightSlot, showsLabel: true, unitLabel: nil) + if name != nil { + rightColumn(name: name, slot: rightSlot, showsLabel: false, unitLabel: nil) } } + } else if let name { + DisplayNameText(name: name) } } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) @@ -179,6 +174,21 @@ private struct SmallFamilyView: View { } } +/// App display name for the small family. Names built from the default +/// `LoopFollow_` pattern truncate at the head so the suffix stays visible. +private struct DisplayNameText: View { + let name: String + + var body: some View { + Text(name) + .font(.system(size: 11, weight: .semibold, design: .rounded)) + .foregroundStyle(.white.opacity(0.65)) + .lineLimit(1) + .minimumScaleFactor(0.8) + .truncationMode(name.hasPrefix("LoopFollow") ? .head : .tail) + } +} + // MARK: - Lock Screen Contract View private struct LockScreenLiveActivityView: View {