From e71af2edb57b69004294f5cf30544729562808bb Mon Sep 17 00:00:00 2001 From: kangddong Date: Sun, 4 Oct 2026 04:35:34 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20Tester=20=ED=83=AD=20=EC=8A=A4=ED=8B=B1?= =?UTF-8?q?=EC=9D=B4=20=ED=83=AD=EB=B0=94=EC=97=90=20=EA=B0=80=EB=A0=A4?= =?UTF-8?q?=EC=A7=80=EB=8A=94=20=EB=AC=B8=EC=A0=9C,=20Live=20Activity=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tester 탭 - 하단 스틱·PS 버튼을 offset 으로 내려 카드 밖(iOS 26 떠 있는 탭바 아래)에 그려지던 문제 수정. 음수 spacing 으로 중앙 행과 겹치게 배치해 레이아웃(카드 높이)에 포함 - D-패드·얼굴 버튼을 양 끝(L1/R1 아래)에 두고 가운데 남는 폭에 스틱이 들어가도록 변경. iPhone SE(가로 667pt) ~ iPhone 17 Pro에서 잘림·겹침 없음 확인 Live Activity - Dynamic Island 확장 화면에서 제조사 이름이 두 번(leading 잘림 + bottom) 보이던 것을 bottom 한 번으로 - 잠금화면 제조사 이름이 잘리지 않도록 축소 허용 (minimumScaleFactor 0.7) - 충전 중 아이콘이 잔량과 무관하게 battery.100.bolt(가득 참)로 보이던 문제 수정: 아이콘은 항상 잔량 기준, 충전 중은 퍼센트 옆 번개로 표시 (minimal 은 번개 아이콘) Co-Authored-By: Claude Opus 5.5 --- .../Sources/Tester/TesterView.swift | 18 +++++----- .../ControllerLiveActivity.swift | 33 ++++++++++--------- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/Controllers/Controllers/Sources/Tester/TesterView.swift b/Controllers/Controllers/Sources/Tester/TesterView.swift index 5c65aab..8f106d5 100644 --- a/Controllers/Controllers/Sources/Tester/TesterView.swift +++ b/Controllers/Controllers/Sources/Tester/TesterView.swift @@ -46,12 +46,14 @@ struct TesterView: View { } Spacer() - .frame(height: 60) - ZStack { + .frame(height: 12) + // offset 은 레이아웃에 반영되지 않아 스틱이 카드 밖(iOS 26 탭바 아래)으로 나갔음. + // 음수 spacing 으로 겹치게 배치해 카드 높이에 포함시킨다. + VStack(spacing: -50) { // 중앙 영역 - HStack(spacing: 40) { + // D-패드·얼굴 버튼을 양 끝(L1/R1 아래)에 두고, 가운데 남는 폭에 스틱 + PS 버튼이 겹쳐 들어간다. + HStack { // D-패드 - Spacer() DPadView(xValue: model.dpadX, yValue: model.dpadY) Spacer() // 얼굴 버튼 @@ -63,19 +65,17 @@ struct TesterView: View { } ControllerButton(systemName: "x.circle", size: 60, isPressed: model.buttonA) } - Spacer() } - .offset(y: -20) - + .padding(.horizontal, 24) + // 하단 버튼 - HStack(spacing: 40) { + HStack(spacing: 24) { // 왼쪽 스틱 (옵션) ThumbstickView(direction: .left, xValue: model.leftStickX, yValue: model.leftStickY) ControllerButton(systemName: "playstation.logo", size: 60, isPressed: model.buttonHome) // 오른쪽 스틱 (옵션) ThumbstickView(direction: .right, xValue: model.rightStickX, yValue: model.rightStickY) } - .offset(y: 60) } } diff --git a/Controllers/ControllersWidget/ControllerLiveActivity.swift b/Controllers/ControllersWidget/ControllerLiveActivity.swift index 673a471..b3d065c 100644 --- a/Controllers/ControllersWidget/ControllerLiveActivity.swift +++ b/Controllers/ControllersWidget/ControllerLiveActivity.swift @@ -26,17 +26,13 @@ struct ControllerLiveActivity: Widget { } dynamicIsland: { context in DynamicIsland { DynamicIslandExpandedRegion(.leading) { - Label { - Text(context.attributes.vendorName) - .font(.caption) - .lineLimit(1) - } icon: { - Image(systemName: "gamecontroller.fill") - } + // 이름은 bottom 진행 바 라벨에 한 번만 표시한다 (leading 은 폭이 좁아 잘림) + Image(systemName: "gamecontroller.fill") + .font(.title3) } DynamicIslandExpandedRegion(.trailing) { Label { - Text(LiveActivityFormat.percentText(context.state.batteryLevel)) + LiveActivityFormat.percentLabel(context.state) .font(.title3) .fontWeight(.semibold) } icon: { @@ -55,10 +51,11 @@ struct ControllerLiveActivity: Widget { } compactLeading: { Image(systemName: "gamecontroller.fill") } compactTrailing: { - Text(LiveActivityFormat.percentText(context.state.batteryLevel)) + LiveActivityFormat.percentLabel(context.state) .foregroundColor(LiveActivityFormat.batteryColor(context.state.batteryLevel)) } minimal: { - Image(systemName: LiveActivityFormat.batteryIconName(for: context.state)) + // 공간이 아이콘 하나뿐이라 충전 중에는 번개, 아니면 잔량 아이콘 + Image(systemName: context.state.batteryState == 1 ? "bolt.fill" : LiveActivityFormat.batteryIconName(for: context.state)) .foregroundColor(LiveActivityFormat.batteryColor(context.state.batteryLevel)) } } @@ -83,6 +80,7 @@ struct LockScreenLiveActivityView: View { .fontWeight(.semibold) .foregroundColor(.white) .lineLimit(1) + .minimumScaleFactor(0.7) ProgressView(value: Double(state.batteryLevel)) .tint(LiveActivityFormat.batteryColor(state.batteryLevel)) @@ -92,7 +90,7 @@ struct LockScreenLiveActivityView: View { HStack(spacing: 4) { Image(systemName: LiveActivityFormat.batteryIconName(for: state)) - Text(LiveActivityFormat.percentText(state.batteryLevel)) + LiveActivityFormat.percentLabel(state) .fontWeight(.semibold) } .font(.headline) @@ -109,12 +107,15 @@ enum LiveActivityFormat { "\(Int(level * 100))%" } - /// 배터리 잔량/상태에 맞는 SF Symbol 이름을 반환한다. + /// "82%" 텍스트. 충전 중(batteryState == 1)이면 번개 아이콘을 붙인다. + static func percentLabel(_ state: ControllerActivityAttributes.ContentState) -> Text { + let text = Text(percentText(state.batteryLevel)) + return state.batteryState == 1 ? text + Text(" \(Image(systemName: "bolt.fill"))") : text + } + + /// 배터리 잔량에 맞는 SF Symbol 이름을 반환한다. + /// (충전 중 표시는 percentLabel 의 번개로 한다. battery.100.bolt 는 잔량과 무관하게 가득 찬 모양이라 쓰지 않음) static func batteryIconName(for state: ControllerActivityAttributes.ContentState) -> String { - // batteryState == 1 (charging) - if state.batteryState == 1 { - return "battery.100.bolt" - } switch state.batteryLevel { case ..<0.1: return "battery.0" case ..<0.25: return "battery.25"