From dd95dcd28f0313e342e86dd925439e3b6d94110a Mon Sep 17 00:00:00 2001 From: songyc macbook Date: Sun, 12 Jul 2026 23:35:09 +0900 Subject: [PATCH] fix(widget): resolve invisible content bug by applying proper containerBackground and dynamic contrast for transparent/tinted themes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On a tinted/transparent (or iOS 18 accented) home screen, the Action Run widget turned into a solid white block with invisible content, while the other widgets rendered fine. Cause: only the Action Run cell paints an opaque tag-color background (WidgetTintedCellBackground) with hardcoded .foregroundStyle(.white) text on top. In any non-.fullColor rendering mode the system re-interprets that opaque fill toward white and the white text disappears — classic white-on-white. The other widgets already use the adaptive AppTheme.surface plus semantic .primary/.secondary text, which is why they were unaffected. Fix, confined to ActionRunCellView: - Read @Environment(\.widgetRenderingMode). In .fullColor keep the tag-color fill + white text (unchanged). In accented/vibrant, drop the opaque fill to a faint translucent tint and render content with the semantic .primary color so the system's tint keeps a clear foreground/background contrast. - No hardcoded .white survives on the tinted path; the container background is still owned by widgetAppTheme()'s .containerBackground(for: .widget) (clear in non-fullColor so the system glass shows through), so this composes safely with iOS 18 home-screen customization. The Live Activity's white-on-tint icon (HaruDanimWidgets) is intentionally left as-is — Live Activities always render full color and aren't subject to home-screen widgetRenderingMode. Verified in the in-app widget preview with a new DEBUG affordance (-widgetRenderMode accented|vibrant, added to WidgetPreviewScreen): forcing accented mode, the Action Run cells now show legible dark icons/labels/values on a faint tint instead of blank white. Build succeeds. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7 --- .../IOS/Views/WidgetPreviewScreen.swift | 11 +++++++++++ myApp/HaruDanim/Widgets/ActionRunWidget.swift | 19 +++++++++++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/myApp/HaruDanim/IOS/Views/WidgetPreviewScreen.swift b/myApp/HaruDanim/IOS/Views/WidgetPreviewScreen.swift index fc082d0..de3b832 100644 --- a/myApp/HaruDanim/IOS/Views/WidgetPreviewScreen.swift +++ b/myApp/HaruDanim/IOS/Views/WidgetPreviewScreen.swift @@ -168,6 +168,16 @@ struct WidgetPreviewScreen: View { } } + /// -widgetRenderMode accented|vibrant → 색조/투명 홈 화면 렌더링 모드를 강제해 + /// white-on-white 대비 문제를 앱 안에서 검증 (기본은 실제와 동일한 fullColor) + private var forcedRenderingMode: WidgetRenderingMode { + switch UserDefaults.standard.string(forKey: "widgetRenderMode") { + case "accented": return .accented + case "vibrant": return .vibrant + default: return .fullColor + } + } + private func widgetBox(_ family: WidgetFamily, @ViewBuilder content: () -> some View) -> some View { let size: CGSize = switch family { case .systemMedium: CGSize(width: 338, height: 158) @@ -181,6 +191,7 @@ struct WidgetPreviewScreen: View { .frame(width: size.width, height: size.height) .background(AppTheme.background) .environment(\.colorScheme, WidgetAppearance.appScheme) + .environment(\.widgetRenderingMode, forcedRenderingMode) .clipShape(RoundedRectangle(cornerRadius: 22, style: .continuous)) .shadow(color: .black.opacity(0.08), radius: 6, y: 2) } diff --git a/myApp/HaruDanim/Widgets/ActionRunWidget.swift b/myApp/HaruDanim/Widgets/ActionRunWidget.swift index 2fe37cf..89c205f 100644 --- a/myApp/HaruDanim/Widgets/ActionRunWidget.swift +++ b/myApp/HaruDanim/Widgets/ActionRunWidget.swift @@ -76,12 +76,17 @@ struct ActionRunProvider: AppIntentTimelineProvider { // MARK: - 뷰 struct ActionRunCellView: View { + /// 홈 화면 렌더링 모드. .fullColor가 아니면(색조/투명/리퀴드 글라스, iOS 18 accented·vibrant) + /// 시스템이 색을 재해석하므로 흰색 하드코딩이 배경째 흰색으로 묻히는 white-on-white가 난다. + @Environment(\.widgetRenderingMode) private var renderingMode let cell: ActionCellSnapshot let periodLabel: String var compact = false /// 셀 하나가 위젯 전체를 차지할 때 (소형) — 위젯 모서리에 맞춰 여백 없이 채움 var fullBleed = false + private var isFullColor: Bool { renderingMode == .fullColor } + var body: some View { Button(intent: RunActionIntent(actionID: cell.id.uuidString)) { VStack(alignment: .leading, spacing: compact ? 2 : 4) { @@ -113,10 +118,11 @@ struct ActionRunCellView: View { .opacity(0.85) .lineLimit(1) } - .foregroundStyle(.white) + // 풀컬러: 꼬리표 색 배경 위 흰 글씨. 색조/투명 모드: 시맨틱 컬러로 그려 시스템 틴트가 대비를 유지. + .foregroundStyle(isFullColor ? AnyShapeStyle(.white) : AnyShapeStyle(.primary)) .padding(fullBleed ? 14 : (compact ? 8 : 12)) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) - .background(WidgetTintedCellBackground(color: cell.color, fullBleed: fullBleed)) + .background(cellBackground) .overlay { if cell.isRunning { if fullBleed { @@ -131,6 +137,15 @@ struct ActionRunCellView: View { } .buttonStyle(.plain) } + + /// 셀 배경: 풀컬러에선 꼬리표 색을 그대로, 색조/투명 모드에선 옅은 반투명으로 깔아 + /// (불투명 색은 흰색 계열로 재해석돼 콘텐츠를 덮음) 시맨틱 콘텐츠가 위에서 대비를 이루게 한다. + @ViewBuilder private var cellBackground: some View { + WidgetTintedCellBackground( + color: isFullColor ? cell.color : cell.color.opacity(0.2), + fullBleed: fullBleed + ) + } } struct ActionRunWidgetView: View {