Conversation
✅ PR의 Assign 자동 지정을 성공했어요! |
🛠️ 이슈와 PR의 Labels 동기화를 스킵했어요. |
Walkthrough앱 전반에 걸쳐 접근성(레이블·힌트·트레이트·작업)과 접근성 환경(감소된 모션·투명도) 반영을 추가하고, 몇몇 컴포넌트에 UI/동작 조정(터치 영역, 안전영역 버튼 배치, 색상, public API 추가)을 적용했습니다. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! 이 PR은 앱 전반에 걸쳐 접근성 지원을 강화하는 데 중점을 둡니다. 다양한 UI 컴포넌트에 스크린 리더를 위한 상세한 정보를 제공하고, 사용자의 접근성 설정에 따라 애니메이션 및 시각적 효과를 조절하여 더 많은 사용자가 앱을 편리하게 이용할 수 있도록 개선했습니다. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
| let accessibilityText: String = "\(titleText) \(calText) 칼로리. \(mealTexts.joined(separator: ", "))" | ||
| let mealTexts: [String] = subMeal.meals.map { mealDisplay(meal: $0) } |
There was a problem hiding this comment.
상수 mealTexts가 정의되기 전에 accessibilityText에서 사용되어 컴파일 오류가 발생합니다. mealTexts의 정의를 accessibilityText 정의 앞으로 옮겨주세요.
| let accessibilityText: String = "\(titleText) \(calText) 칼로리. \(mealTexts.joined(separator: ", "))" | |
| let mealTexts: [String] = subMeal.meals.map { mealDisplay(meal: $0) } | |
| let mealTexts: [String] = subMeal.meals.map { mealDisplay(meal: $0) } | |
| let accessibilityText: String = "\(titleText) \(calText) 칼로리. \(mealTexts.joined(separator: ", "))" |
| .accessibilityAction(.escape) { onDismiss() } | ||
|
|
||
| if #available(iOS 26.0, *) { | ||
| if #available(iOS 26.0, *), !reduceTransparency { |
There was a problem hiding this comment.
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (3)
Projects/Feature/AddWidgetFeature/Sources/AddWidgetView.swift (1)
85-86: 일관성을 위해.isButton트레이트 추가를 고려해보세요.
AllergySettingView에서는.accessibilityAddTraits(.isButton)을 명시적으로 추가하고 있습니다.Button내부에서accessibilityElement(children: .ignore)를 사용할 때 버튼 트레이트가 자동으로 상속되지 않을 수 있으므로, 일관성과 명확성을 위해 트레이트를 추가하는 것이 좋습니다.♻️ 제안된 수정
.accessibilityElement(children: .ignore) .accessibilityLabel("\(widget.kind.title), \(widget.family.title)") + .accessibilityAddTraits(.isButton)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Projects/Feature/AddWidgetFeature/Sources/AddWidgetView.swift` around lines 85 - 86, In AddWidgetView update the accessibility setup to explicitly add the button trait: when using .accessibilityElement(children: .ignore) and setting .accessibilityLabel("\(widget.kind.title), \(widget.family.title)"), also call .accessibilityAddTraits(.isButton) (matching AllergySettingView) so the element is always exposed as a button to assistive technologies; locate the chain where accessibilityElement and accessibilityLabel are applied and append accessibilityAddTraits(.isButton).Projects/UserInterface/DesignSystem/Sources/TWBottomSheet/TWBottomSheet.swift (1)
51-51: 감소된 투명도 모드에서 반투명 오버레이가 유지됩니다.Line 51에서
reduceTransparency == true일 때Color.black.opacity(0.6)를 사용하면 배경이 계속 비쳐 보입니다. 이 분기는 불투명 색상을 써서 대비를 확실히 높이는 편이 더 안전합니다.제안 diff
- (reduceTransparency ? Color.black.opacity(0.6) : Color.lightBox) + (reduceTransparency ? Color.black : Color.lightBox)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Projects/UserInterface/DesignSystem/Sources/TWBottomSheet/TWBottomSheet.swift` at line 51, In TWBottomSheet.swift the reduceTransparency branch currently uses Color.black.opacity(0.6) which still allows background bleed; change the branch handling for reduceTransparency (the variable/condition named reduceTransparency in the TWBottomSheet overlay code) to use a fully opaque color (e.g., Color.black without opacity) or another high-contrast opaque color to ensure the overlay is not translucent; update the conditional expression that returns (reduceTransparency ? Color.black.opacity(0.6) : Color.lightBox) to return an opaque color when reduceTransparency is true and verify visually or with the provided [request_verification].Projects/UserInterface/DesignSystem/Sources/TWTextField/TWTextField.swift (1)
35-35: 빈 placeholder 기본값일 때 접근성 라벨이 비어질 수 있습니다.Line 35는
placeholder를 그대로 라벨로 쓰므로, 기본값("") 경로에서 무라벨 컨트롤이 될 수 있습니다. 기본 fallback 라벨을 두는 편이 안전합니다.개선 예시
- .accessibilityLabel(placeholder) + .accessibilityLabel(placeholder.isEmpty ? "텍스트 입력" : placeholder)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Projects/UserInterface/DesignSystem/Sources/TWTextField/TWTextField.swift` at line 35, placeholder가 비어있을 때 접근성 라벨이 빈 문자열이 되는 문제입니다; TWTextField에서 .accessibilityLabel(placeholder)를 그대로 쓰는 대신 placeholder가 비어있으면 안전한 fallback 라벨을 사용하도록 수정하세요 (예: localized 기본 라벨 또는 accessibilityPlaceholder 변수). 즉 TWTextField의 placeholder 값을 검사하고 빈 문자열일 경우 대체 텍스트를 전달하도록 변경하세요.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@Projects/Feature/MainFeature/Sources/MainView.swift`:
- Around line 99-107: The dismiss path currently passes a fixed animation
(.default) when calling viewStore.send(.hideReviewToast, animation: .default)
from the ReviewToast usage, which ignores the user's reduce motion setting;
change the view to read the accessibilityReduceMotion environment (e.g.
`@Environment`(\.accessibilityReduceMotion) var reduceMotion) and conditionally
pass animation: reduceMotion ? nil : .default (or use withAnimation only when
reduceMotion is false) for all places where you call
viewStore.send(.hideReviewToast, animation: .default) and similar dismiss/send
calls inside ReviewToast usage so dismiss respects reduce-motion.
- Line 201: Remove the .accessibilityRemoveTraits(.isButton) modifier from the
menu trigger in MainView so VoiceOver can correctly announce the control as a
button; locate the occurrence inside the MainView (the menu trigger in the view
body) and delete that modifier (do not replace it with another trait removal),
ensuring the element retains its default .isButton accessibility trait.
In `@Projects/Feature/MealFeature/Sources/Weekly/WeeklyMealView.swift`:
- Around line 220-229: The accessibility action ".accessibilityAction(named:
\"이미지로 복사\")" is always registered but only works on iOS 16+; move the
availability check outwards so the modifier is only applied on iOS 16 and above.
Concretely, wrap the view/modifier application that adds the accessibilityAction
in an if `#available`(iOS 16.0, *) branch (or apply the modifier conditionally
using a Group) so that ImageRenderer(content: mealCardView), renderer.scale =
displayScale, UIPasteboard.general.image assignment and
TWLog.event(ShareMealImageEventLog()) are only reachable when the action is
actually registered. Ensure you reference the existing symbols
accessibilityAction, ImageRenderer, mealCardView, displayScale,
UIPasteboard.general, TWLog and ShareMealImageEventLog when updating the view
modifiers.
In `@Projects/Feature/SchoolSettingFeature/Sources/SchoolSettingView.swift`:
- Around line 50-52: Several animation calls in SchoolSettingView still force
motion; update every hardcoded .animation(.default, ...) and every withAnimation
{ ... } to respect the reduceMotion flag. Replace occurrences of
.animation(.default, value: ...) with .animation(reduceMotion ? .none :
.default, value: ...) and change withAnimation { ... } to
withAnimation(reduceMotion ? nil : .default) { ... } (or
withAnimation(reduceMotion ? nil : .default) around the exact closure) for the
remaining spots (the other .animation usages and all withAnimation blocks in
SchoolSettingView) so all paths consistently honor reduceMotion.
In `@Projects/Feature/TimeTableFeature/Sources/Weekly/WeeklyTimeTableCore.swift`:
- Around line 36-45: Public init in WeeklyTimeTable must enforce array-length
invariants: inside WeeklyTimeTable.init validate that weekdays.count ==
fullWeekdays.count && weekdays.count == dates.count && weekdays.count ==
subjects.count (and if todayIndex != nil assert 0 <= todayIndex! <
weekdays.count); on violation call preconditionFailure (or throw/failable init
per project style) with a clear message so malformed inputs fail early; update
the initializer that assigns self.weekdays, self.fullWeekdays, self.dates,
self.periods, self.subjects, and self.todayIndex to run these checks before
assignment.
In `@Projects/Feature/TimeTableFeature/Sources/Weekly/WeeklyTimeTableView.swift`:
- Around line 265-267: The accessibility label construction for accessLabel
currently appends ", 오늘" only when subject is non-empty; modify the ternary
branches that build accessLabel (the subject.isEmpty case and the non-empty case
in WeeklyTimeTableView where accessLabel is formed using fullWeekdayName,
period, subject and isToday) so that both branches include the isToday context —
e.g., append the same ", 오늘" suffix when isToday is true even for the
"\(fullWeekdayName) \(period)교시 수업 없음" branch — ensuring consistent vocal
feedback.
In
`@Projects/UserInterface/DesignSystem/Sources/TWBottomSheet/TWBottomSheet.swift`:
- Line 93: Add a performAnimation helper that checks reduceMotion and either
runs the closure directly or wraps it in withAnimation, then replace all
explicit withAnimation(...) calls in this file (the spots currently toggling
isShowing / related state) to call performAnimation { ... } so every state
change respects reduceMotion; keep the existing .animation(reduceMotion ? .none
: .default, value: isShowing) for implicit animations but ensure all explicit
withAnimation usages are swapped to performAnimation to enforce the
accessibility setting.
In `@Projects/UserInterface/DesignSystem/Sources/TWTextField/TWTextField.swift`:
- Around line 63-65: The withAnimation call incorrectly disables animation in
both branches because it uses `reduceMotion ? .none : nil`; change it to enable
animation only when `reduceMotion` is false by swapping branches (e.g.,
`withAnimation(reduceMotion ? nil : .default) { text = "" }`), updating the
`withAnimation` invocation in TWTextField where `text` is cleared and
`reduceMotion` is checked so animation is suppressed only when `reduceMotion` is
true.
---
Nitpick comments:
In `@Projects/Feature/AddWidgetFeature/Sources/AddWidgetView.swift`:
- Around line 85-86: In AddWidgetView update the accessibility setup to
explicitly add the button trait: when using .accessibilityElement(children:
.ignore) and setting .accessibilityLabel("\(widget.kind.title),
\(widget.family.title)"), also call .accessibilityAddTraits(.isButton) (matching
AllergySettingView) so the element is always exposed as a button to assistive
technologies; locate the chain where accessibilityElement and accessibilityLabel
are applied and append accessibilityAddTraits(.isButton).
In
`@Projects/UserInterface/DesignSystem/Sources/TWBottomSheet/TWBottomSheet.swift`:
- Line 51: In TWBottomSheet.swift the reduceTransparency branch currently uses
Color.black.opacity(0.6) which still allows background bleed; change the branch
handling for reduceTransparency (the variable/condition named reduceTransparency
in the TWBottomSheet overlay code) to use a fully opaque color (e.g.,
Color.black without opacity) or another high-contrast opaque color to ensure the
overlay is not translucent; update the conditional expression that returns
(reduceTransparency ? Color.black.opacity(0.6) : Color.lightBox) to return an
opaque color when reduceTransparency is true and verify visually or with the
provided [request_verification].
In `@Projects/UserInterface/DesignSystem/Sources/TWTextField/TWTextField.swift`:
- Line 35: placeholder가 비어있을 때 접근성 라벨이 빈 문자열이 되는 문제입니다; TWTextField에서
.accessibilityLabel(placeholder)를 그대로 쓰는 대신 placeholder가 비어있으면 안전한 fallback 라벨을
사용하도록 수정하세요 (예: localized 기본 라벨 또는 accessibilityPlaceholder 변수). 즉 TWTextField의
placeholder 값을 검사하고 빈 문자열일 경우 대체 텍스트를 전달하도록 변경하세요.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 04f30487-8e3d-4f00-bb72-a5f8aa251c68
📒 Files selected for processing (14)
Projects/Feature/AddWidgetFeature/Sources/AddWidgetView.swiftProjects/Feature/AllergySettingFeature/Sources/AllergySettingView.swiftProjects/Feature/MainFeature/Sources/Components/ReviewToast.swiftProjects/Feature/MainFeature/Sources/MainView.swiftProjects/Feature/MealFeature/Sources/Weekly/WeeklyMealView.swiftProjects/Feature/SchoolSettingFeature/Sources/SchoolSettingView.swiftProjects/Feature/SettingsFeature/Sources/SettingsView.swiftProjects/Feature/TimeTableFeature/Sources/Weekly/WeeklyTimeTableCore.swiftProjects/Feature/TimeTableFeature/Sources/Weekly/WeeklyTimeTableView.swiftProjects/UserInterface/DesignSystem/Resources/Colors.xcassets/DesignSystem/UnselectedPrimary.colorset/Contents.jsonProjects/UserInterface/DesignSystem/Sources/TWBottomSheet/TWBottomSheet.swiftProjects/UserInterface/DesignSystem/Sources/TWButton/View+twBackButton.swiftProjects/UserInterface/DesignSystem/Sources/TWTextField/TWTextField.swiftProjects/UserInterface/DesignSystem/Sources/TopTabbar/TopTabbar.swift
| ReviewToast( | ||
| onTap: { | ||
| viewStore.send(.requestReview) | ||
| TWLog.event(ClickReviewEventLog()) | ||
| }, | ||
| onDismiss: { | ||
| viewStore.send(.hideReviewToast, animation: .default) | ||
| } | ||
| ) |
There was a problem hiding this comment.
리뷰 토스트 dismiss 경로가 reduceMotion 설정을 완전히 따르지 않습니다.
Line 105(그리고 동일한 Line 127)에서 animation: .default를 고정 사용하면, reduceMotion 활성 시에도 dismiss 애니메이션이 발생할 수 있습니다.
🔧 제안 수정
ReviewToast(
onTap: {
viewStore.send(.requestReview)
TWLog.event(ClickReviewEventLog())
},
onDismiss: {
- viewStore.send(.hideReviewToast, animation: .default)
+ viewStore.send(.hideReviewToast, animation: reduceMotion ? .none : .default)
}
)
...
DispatchQueue.main.asyncAfter(deadline: .now() + 7.5) {
- viewStore.send(.hideReviewToast, animation: .default)
+ viewStore.send(.hideReviewToast, animation: reduceMotion ? .none : .default)
}Also applies to: 111-119
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Projects/Feature/MainFeature/Sources/MainView.swift` around lines 99 - 107,
The dismiss path currently passes a fixed animation (.default) when calling
viewStore.send(.hideReviewToast, animation: .default) from the ReviewToast
usage, which ignores the user's reduce motion setting; change the view to read
the accessibilityReduceMotion environment (e.g.
`@Environment`(\.accessibilityReduceMotion) var reduceMotion) and conditionally
pass animation: reduceMotion ? nil : .default (or use withAnimation only when
reduceMotion is false) for all places where you call
viewStore.send(.hideReviewToast, animation: .default) and similar dismiss/send
calls inside ReviewToast usage so dismiss respects reduce-motion.
| } | ||
| .accessibilityLabel("날짜 선택") | ||
| .accessibilityHint("클릭하여 날짜를 선택할 수 있습니다") | ||
| .accessibilityRemoveTraits(.isButton) |
There was a problem hiding this comment.
메뉴 트리거에서 버튼 트레이트 제거는 접근성 인지성을 떨어뜨릴 수 있습니다.
Line 201의 .accessibilityRemoveTraits(.isButton)로 인해 상호작용 가능한 컨트롤임을 VoiceOver가 덜 명확히 전달할 수 있습니다. 이 modifier는 제거하는 편이 안전합니다.
🔧 제안 수정
- .accessibilityRemoveTraits(.isButton)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| .accessibilityRemoveTraits(.isButton) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Projects/Feature/MainFeature/Sources/MainView.swift` at line 201, Remove the
.accessibilityRemoveTraits(.isButton) modifier from the menu trigger in MainView
so VoiceOver can correctly announce the control as a button; locate the
occurrence inside the MainView (the menu trigger in the view body) and delete
that modifier (do not replace it with another trait removal), ensuring the
element retains its default .isButton accessibility trait.
| .accessibilityAction(named: "이미지로 복사") { | ||
| if #available(iOS 16.0, *) { | ||
| let renderer = ImageRenderer(content: mealCardView) | ||
| renderer.scale = displayScale | ||
| if let image = renderer.uiImage { | ||
| UIPasteboard.general.image = image | ||
| TWLog.event(ShareMealImageEventLog()) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 접근성 액션의 가용성 처리 위치 확인
nl -ba Projects/Feature/MealFeature/Sources/Weekly/WeeklyMealView.swift | sed -n '214,232p'
# 기대 결과:
# - accessibilityAction(named: "이미지로 복사") modifier가 조건 없이 붙어 있음
# - `#available`(iOS 16.0, *) 체크가 액션 클로저 내부에만 존재
# 이 경우 iOS 16 미만에서 no-op 액션 노출 가능성 있음Repository: todaywhat/TodayWhat-iOS
Length of output: 107
🏁 Script executed:
cat -n Projects/Feature/MealFeature/Sources/Weekly/WeeklyMealView.swift | sed -n '214,232p'Repository: todaywhat/TodayWhat-iOS
Length of output: 1037
iOS 16 미만에서 "이미지로 복사" 접근성 액션이 노출되지만 실제로 동작하지 않습니다.
Line 220의 .accessibilityAction(named: "이미지로 복사") 수정자는 조건 없이 항상 등록되고, iOS 16 가용성 체크는 액션 클로저 내부(line 221)에만 있습니다. 이로 인해 iOS 16 미만 사용자의 접근성 트리에 no-op 액션이 노출되어 혼란을 초래할 수 있습니다. 액션 등록 자체를 조건부로 분기하여 iOS 16 미만에서는 액션을 노출하지 않는 것이 안전합니다.
제안 수정안
- mealCardView
- .accessibilityElement(children: .combine)
- .accessibilityLabel(accessibilityText)
- .accessibilityAction(named: "텍스트로 복사") {
- UIPasteboard.general.string = shareText
- TWLog.event(ShareMealEventLog())
- }
- .accessibilityAction(named: "이미지로 복사") {
- if `#available`(iOS 16.0, *) {
- let renderer = ImageRenderer(content: mealCardView)
- renderer.scale = displayScale
- if let image = renderer.uiImage {
- UIPasteboard.general.image = image
- TWLog.event(ShareMealImageEventLog())
- }
- }
- }
+ let baseCard = mealCardView
+ .accessibilityElement(children: .combine)
+ .accessibilityLabel(accessibilityText)
+ .accessibilityAction(named: "텍스트로 복사") {
+ UIPasteboard.general.string = shareText
+ TWLog.event(ShareMealEventLog())
+ }
+
+ Group {
+ if `#available`(iOS 16.0, *) {
+ baseCard
+ .accessibilityAction(named: "이미지로 복사") {
+ let renderer = ImageRenderer(content: mealCardView)
+ renderer.scale = displayScale
+ if let image = renderer.uiImage {
+ UIPasteboard.general.image = image
+ TWLog.event(ShareMealImageEventLog())
+ }
+ }
+ } else {
+ baseCard
+ }
+ }
.contextMenu {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Projects/Feature/MealFeature/Sources/Weekly/WeeklyMealView.swift` around
lines 220 - 229, The accessibility action ".accessibilityAction(named: \"이미지로
복사\")" is always registered but only works on iOS 16+; move the availability
check outwards so the modifier is only applied on iOS 16 and above. Concretely,
wrap the view/modifier application that adds the accessibilityAction in an if
`#available`(iOS 16.0, *) branch (or apply the modifier conditionally using a
Group) so that ImageRenderer(content: mealCardView), renderer.scale =
displayScale, UIPasteboard.general.image assignment and
TWLog.event(ShareMealImageEventLog()) are only reachable when the action is
actually registered. Ensure you reference the existing symbols
accessibilityAction, ImageRenderer, mealCardView, displayScale,
UIPasteboard.general, TWLog and ShareMealImageEventLog when updating the view
modifiers.
| .animation(reduceMotion ? .none : .default, value: viewStore.grade) | ||
| .animation(reduceMotion ? .none : .default, value: viewStore.class) | ||
| .animation(reduceMotion ? .none : .default, value: viewStore.school) |
There was a problem hiding this comment.
감소된 모션 처리가 일부 경로에서만 적용됩니다.
Line 50~52는 잘 반영됐지만, Line 56/71/131/222/245의 animation: .default 및 withAnimation은 여전히 모션을 강제합니다. 접근성 설정 일관성을 위해 동일하게 reduceMotion 분기 적용이 필요합니다.
🔧 제안 수정
.onChange(of: focusField) { newValue in
- viewStore.send(.schoolFocusedChanged(newValue == .school), animation: .default)
+ viewStore.send(.schoolFocusedChanged(newValue == .school), animation: reduceMotion ? .none : .default)
}
...
.onAppear {
viewStore.send(.onAppear)
- withAnimation {
+ withAnimation(reduceMotion ? .none : .default) {
focusField = .school
}
}
...
.onTapGesture {
- viewStore.send(.majorTextFieldDidTap, animation: .default)
+ viewStore.send(.majorTextFieldDidTap, animation: reduceMotion ? .none : .default)
focusField = nil
}
...
.onTapGesture {
- viewStore.send(.schoolRowDidSelect(school), animation: .default)
+ viewStore.send(.schoolRowDidSelect(school), animation: reduceMotion ? .none : .default)
focusField = .grade
}
...
TWButton(title: viewStore.nextButtonTitle, style: .wide) {
- viewStore.send(.nextButtonDidTap, animation: .default)
+ viewStore.send(.nextButtonDidTap, animation: reduceMotion ? .none : .default)
focusField = nil
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Projects/Feature/SchoolSettingFeature/Sources/SchoolSettingView.swift` around
lines 50 - 52, Several animation calls in SchoolSettingView still force motion;
update every hardcoded .animation(.default, ...) and every withAnimation { ... }
to respect the reduceMotion flag. Replace occurrences of .animation(.default,
value: ...) with .animation(reduceMotion ? .none : .default, value: ...) and
change withAnimation { ... } to withAnimation(reduceMotion ? nil : .default) {
... } (or withAnimation(reduceMotion ? nil : .default) around the exact closure)
for the remaining spots (the other .animation usages and all withAnimation
blocks in SchoolSettingView) so all paths consistently honor reduceMotion.
| public init( | ||
| weekdays: [String], | ||
| fullWeekdays: [String], | ||
| dates: [String], | ||
| periods: [Int], | ||
| subjects: [[String]], | ||
| todayIndex: Int? = nil | ||
| ) { | ||
| self.weekdays = weekdays | ||
| self.fullWeekdays = fullWeekdays |
There was a problem hiding this comment.
WeeklyTimeTable 배열 길이 불변식을 강제해주세요.
weekdays, fullWeekdays, dates, subjects 길이가 불일치하면 이후 인덱싱에서 런타임 크래시가 발생할 수 있습니다. 공개 이니셜라이저에서 길이 검증을 넣어 불변식을 보장하는 게 안전합니다.
수정 예시
public init(
weekdays: [String],
fullWeekdays: [String],
dates: [String],
periods: [Int],
subjects: [[String]],
todayIndex: Int? = nil
) {
+ precondition(weekdays.count == fullWeekdays.count, "weekdays/fullWeekdays count mismatch")
+ precondition(weekdays.count == dates.count, "weekdays/dates count mismatch")
+ precondition(weekdays.count == subjects.count, "weekdays/subjects count mismatch")
+
self.weekdays = weekdays
self.fullWeekdays = fullWeekdays
self.dates = dates
self.periods = periods
self.subjects = subjects
self.todayIndex = todayIndex
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public init( | |
| weekdays: [String], | |
| fullWeekdays: [String], | |
| dates: [String], | |
| periods: [Int], | |
| subjects: [[String]], | |
| todayIndex: Int? = nil | |
| ) { | |
| self.weekdays = weekdays | |
| self.fullWeekdays = fullWeekdays | |
| public init( | |
| weekdays: [String], | |
| fullWeekdays: [String], | |
| dates: [String], | |
| periods: [Int], | |
| subjects: [[String]], | |
| todayIndex: Int? = nil | |
| ) { | |
| precondition(weekdays.count == fullWeekdays.count, "weekdays/fullWeekdays count mismatch") | |
| precondition(weekdays.count == dates.count, "weekdays/dates count mismatch") | |
| precondition(weekdays.count == subjects.count, "weekdays/subjects count mismatch") | |
| self.weekdays = weekdays | |
| self.fullWeekdays = fullWeekdays | |
| self.dates = dates | |
| self.periods = periods | |
| self.subjects = subjects | |
| self.todayIndex = todayIndex | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Projects/Feature/TimeTableFeature/Sources/Weekly/WeeklyTimeTableCore.swift`
around lines 36 - 45, Public init in WeeklyTimeTable must enforce array-length
invariants: inside WeeklyTimeTable.init validate that weekdays.count ==
fullWeekdays.count && weekdays.count == dates.count && weekdays.count ==
subjects.count (and if todayIndex != nil assert 0 <= todayIndex! <
weekdays.count); on violation call preconditionFailure (or throw/failable init
per project style) with a clear message so malformed inputs fail early; update
the initializer that assigns self.weekdays, self.fullWeekdays, self.dates,
self.periods, self.subjects, and self.todayIndex to run these checks before
assignment.
| let accessLabel: String = subject.isEmpty | ||
| ? "\(fullWeekdayName) \(period)교시 수업 없음" | ||
| : "\(fullWeekdayName) \(period)교시 \(subject)\(isToday ? ", 오늘" : "")" |
There was a problem hiding this comment.
빈 셀 접근성 라벨에도 ‘오늘’ 맥락을 일관되게 포함해주세요.
현재는 과목이 있을 때만 , 오늘이 붙고, 수업 없음 케이스에는 오늘 정보가 빠집니다. 오늘 컬럼 탐색 시 음성 피드백 일관성이 깨집니다.
수정 예시
- let accessLabel: String = subject.isEmpty
- ? "\(fullWeekdayName) \(period)교시 수업 없음"
- : "\(fullWeekdayName) \(period)교시 \(subject)\(isToday ? ", 오늘" : "")"
+ let todaySuffix = isToday ? ", 오늘" : ""
+ let accessLabel: String = subject.isEmpty
+ ? "\(fullWeekdayName) \(period)교시 수업 없음\(todaySuffix)"
+ : "\(fullWeekdayName) \(period)교시 \(subject)\(todaySuffix)"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| let accessLabel: String = subject.isEmpty | |
| ? "\(fullWeekdayName) \(period)교시 수업 없음" | |
| : "\(fullWeekdayName) \(period)교시 \(subject)\(isToday ? ", 오늘" : "")" | |
| let todaySuffix = isToday ? ", 오늘" : "" | |
| let accessLabel: String = subject.isEmpty | |
| ? "\(fullWeekdayName) \(period)교시 수업 없음\(todaySuffix)" | |
| : "\(fullWeekdayName) \(period)교시 \(subject)\(todaySuffix)" |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Projects/Feature/TimeTableFeature/Sources/Weekly/WeeklyTimeTableView.swift`
around lines 265 - 267, The accessibility label construction for accessLabel
currently appends ", 오늘" only when subject is non-empty; modify the ternary
branches that build accessLabel (the subject.isEmpty case and the non-empty case
in WeeklyTimeTableView where accessLabel is formed using fullWeekdayName,
period, subject and isToday) so that both branches include the isToday context —
e.g., append the same ", 오늘" suffix when isToday is true even for the
"\(fullWeekdayName) \(period)교시 수업 없음" branch — ensuring consistent vocal
feedback.
| .ignoresSafeArea() | ||
| } | ||
| .animation(.default, value: isShowing) | ||
| .animation(reduceMotion ? .none : .default, value: isShowing) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# read-only verification: explicit animation 경로 확인
rg -n --type=swift -C2 'withAnimation\s*\(' Projects/UserInterface/DesignSystem/Sources/TWBottomSheet/TWBottomSheet.swift
rg -n --type=swift -C2 'accessibilityReduceMotion|\.animation\s*\(' Projects/UserInterface/DesignSystem/Sources/TWBottomSheet/TWBottomSheet.swiftRepository: todaywhat/TodayWhat-iOS
Length of output: 865
🏁 Script executed:
#!/bin/bash
# Find all withAnimation calls with line numbers
rg -n 'withAnimation' Projects/UserInterface/DesignSystem/Sources/TWBottomSheet/TWBottomSheet.swift
# Check file length and structure
wc -l Projects/UserInterface/DesignSystem/Sources/TWBottomSheet/TWBottomSheet.swift
# Show the entire file context to understand all animations
cat -n Projects/UserInterface/DesignSystem/Sources/TWBottomSheet/TWBottomSheet.swiftRepository: todaywhat/TodayWhat-iOS
Length of output: 4915
모든 애니메이션이 Reduce Motion 설정을 존중하도록 수정 필요
Line 93의 .animation(reduceMotion ? .none : .default, value: isShowing)만으로는 불충분합니다. 이는 오직 isShowing 상태 변경으로 인한 애니메이션만 제어하고, 명시적 withAnimation 호출(Line 16, 21, 26, 54, 81)은 접근성 설정과 관계없이 계속 실행됩니다.
performAnimation 헬퍼 함수를 추가하여 모든 애니메이션이 reduceMotion 설정을 존중하도록 리팩토링하세요:
제안 diff
struct TWBottomSheet<T: View>: ViewModifier {
`@Binding` var isShowing: Bool
`@State` var dragHeight: CGFloat = 0
var content: () -> T
var height: CGFloat
var backgroundColor: Color
`@Environment`(\.accessibilityReduceMotion) private var reduceMotion
`@Environment`(\.accessibilityReduceTransparency) private var reduceTransparency
+
+ private func performAnimation(
+ _ animation: Animation? = .default,
+ _ action: `@escaping` () -> Void
+ ) {
+ if reduceMotion {
+ action()
+ } else {
+ withAnimation(animation, action)
+ }
+ }
var sheetDragging: some Gesture {
DragGesture(minimumDistance: 0, coordinateSpace: .global)
.onChanged { value in
- withAnimation(.spring()) {
+ performAnimation(.spring()) {
dragHeight = min(30, -value.translation.height)
}
}
.onEnded { value in
- withAnimation(.spring()) {
+ performAnimation(.spring()) {
dragHeight = 0
}
let verticalAmount = value.translation.height
if verticalAmount > 100 {
- withAnimation {
+ performAnimation {
isShowing = false
}
}
}
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@Projects/UserInterface/DesignSystem/Sources/TWBottomSheet/TWBottomSheet.swift`
at line 93, Add a performAnimation helper that checks reduceMotion and either
runs the closure directly or wraps it in withAnimation, then replace all
explicit withAnimation(...) calls in this file (the spots currently toggling
isShowing / related state) to call performAnimation { ... } so every state
change respects reduceMotion; keep the existing .animation(reduceMotion ? .none
: .default, value: isShowing) for implicit animations but ensure all explicit
withAnimation usages are swapped to performAnimation to enforce the
accessibility setting.
| withAnimation(reduceMotion ? .none : nil) { | ||
| text = "" | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In SwiftUI withAnimation(_ animation: Animation?, _ body: () -> Result), are .noneandnilequivalent forAnimation?, and does reduceMotion ? .none : nil always disable animation?
💡 Result:
.none and nil are not equivalent values for Animation?:
.noneis a realAnimationvalue that represents “no animation”.nilmeans “no animation specified” (i.e., the transaction’s animation is absent/cleared). In SwiftUI APIs that takeAnimation?,nildisables the animation. [1][2]
So reduceMotion ? .none : nil does not mean “disable when reduced motion, otherwise use the default” — it means “no animation in both branches”, i.e. it will always disable animation. [1][3]
If you want “default animation unless reduced motion”, do e.g.:
withAnimation(reduceMotion ? nil : .default) { ... }(or your chosen animation), sincenildisables. [1]
Sources:
[1] Use Your Loaf — Reducing Motion of Animations (shows withAnimation(linearAnimation) where returning nil “disables the animation”)
[2] Swift Forums (Composable Architecture thread) — notes that passing nil to SwiftUI’s animation-optional APIs means “do not animate at all”
[3] Fatbobman — notes (re SwiftUI animation optionals) “Both .none and nil indicate no animation”
reduceMotion 조건부에도 불구하고 항상 애니메이션이 비활성화됩니다.
Line 63의 reduceMotion ? .none : nil은 두 분기 모두 애니메이션을 비활성화하므로, 의도한 조건부 애니메이션이 작동하지 않습니다. reduceMotion이 참이어도 거짓이어도 항상 애니메이션이 꺼집니다.
수정 제안
- withAnimation(reduceMotion ? .none : nil) {
+ withAnimation(reduceMotion ? nil : .default) {
text = ""
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Projects/UserInterface/DesignSystem/Sources/TWTextField/TWTextField.swift`
around lines 63 - 65, The withAnimation call incorrectly disables animation in
both branches because it uses `reduceMotion ? .none : nil`; change it to enable
animation only when `reduceMotion` is false by swapping branches (e.g.,
`withAnimation(reduceMotion ? nil : .default) { text = "" }`), updating the
`withAnimation` invocation in TWTextField where `text` is cleared and
`reduceMotion` is checked so animation is suppressed only when `reduceMotion` is
true.
There was a problem hiding this comment.
♻️ Duplicate comments (1)
Projects/UserInterface/DesignSystem/Sources/TWTextField/TWTextField.swift (1)
63-65:⚠️ Potential issue | 🟡 Minor
withAnimation분기 로직이 여전히 항상 애니메이션을 비활성화합니다.Line 63의
reduceMotion ? .none : nil은 두 분기 모두 “애니메이션 없음”으로 해석되어,reduceMotion == false여도 텍스트 삭제 애니메이션이 실행되지 않습니다.수정 제안
- withAnimation(reduceMotion ? .none : nil) { + withAnimation(reduceMotion ? nil : .default) { text = "" }SwiftUI의 `withAnimation(_ animation: Animation?, _ body:)`에서 `nil`과 `.none`의 의미 차이, 그리고 기본 애니메이션을 활성화하는 권장 패턴을 확인해 주세요.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Projects/UserInterface/DesignSystem/Sources/TWTextField/TWTextField.swift` around lines 63 - 65, The withAnimation call currently uses reduceMotion ? .none : nil which results in no animation in both branches; change it to pass a real animation when reduceMotion is false (for example withAnimation(reduceMotion ? nil : .default) { text = "" }) or explicitly branch (if reduceMotion { withAnimation(nil) { text = "" } } else { withAnimation { text = "" } }) so that reduceMotion, withAnimation and text in TWTextField.swift behave correctly.
🧹 Nitpick comments (5)
Projects/Feature/AddWidgetFeature/Sources/AddWidgetView.swift (3)
179-181:DispatchQueue.main.asyncAfter대신Task기반 접근 고려.현재 구현은 뷰가 사라진 후에도 비동기 블록이 실행될 수 있습니다.
@AccessibilityFocusState는 뷰가 없으면 무시되므로 크래시는 발생하지 않지만, 구조화된 동시성을 위해Task를 사용하면 뷰 생명주기와 더 잘 연동됩니다.♻️ Task 기반 대안
.onAppear { timerActive = true elapsedTime = 0 - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - isGuideFocused = true - } } + .task { + try? await Task.sleep(nanoseconds: 500_000_000) + isGuideFocused = true + }
Task는 뷰가 사라지면 자동으로 취소되어 불필요한 실행을 방지합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Projects/Feature/AddWidgetFeature/Sources/AddWidgetView.swift` around lines 179 - 181, Replace the DispatchQueue.main.asyncAfter block with a Task-based delay to tie the work to structured concurrency and the view lifecycle: in the code using isGuideFocused (the `@AccessibilityFocusState` binding), remove DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { isGuideFocused = true } and instead create a Task that awaits Task.sleep(nanoseconds: 500_000_000) and then sets isGuideFocused on the main actor (e.g., await MainActor.run { isGuideFocused = true }) so the work is cancellable when the view disappears.
146-147: 접근성 레이블이 중복될 수 있습니다.
Text(currentGuideText)는 기본적으로 텍스트 내용을 접근성 레이블로 사용합니다..accessibilityLabel(currentGuideText)를 추가하면 동일한 값을 중복 설정하게 됩니다. 의도적으로 명시한 것이라면 문제없지만, 불필요한 코드일 수 있습니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Projects/Feature/AddWidgetFeature/Sources/AddWidgetView.swift` around lines 146 - 147, Text(currentGuideText) already exposes its string to VoiceOver, so calling .accessibilityLabel(currentGuideText) duplicates the label; remove the redundant modifier on the view that chains .accessibilityFocused($isGuideFocused) and .accessibilityLabel(currentGuideText) (or replace the label argument with a distinct, intentional string only if you need a different spoken value) so keep .accessibilityFocused($isGuideFocused) and either delete .accessibilityLabel(currentGuideText) or supply a unique accessibility label.
64-87: 접근성 구현이 적절합니다.위젯 프리뷰를 VoiceOver에서 숨기고 행 전체를 하나의 접근성 요소로 처리한 것은 좋은 접근입니다.
kind.title과family.title이 이미 한국어 지역화된 문자열을 반환하므로 접근성 레이블로 적합합니다.선택적으로 개선할 수 있는 부분:
- 버튼 동작에 대한 힌트 추가 (예:
.accessibilityHint("위젯 추가 가이드를 표시합니다")).accessibilityAddTraits(.isButton)추가로 버튼임을 명확히 할 수 있습니다 (children: .ignore사용 시 기존 트레이트가 유지되지 않을 수 있음)♻️ 선택적 개선안
.padding(.horizontal, 16) .accessibilityElement(children: .ignore) .accessibilityLabel("\(widget.kind.title), \(widget.family.title)") + .accessibilityAddTraits(.isButton) + .accessibilityHint("위젯 추가 가이드를 표시합니다")🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Projects/Feature/AddWidgetFeature/Sources/AddWidgetView.swift` around lines 64 - 87, 현재 widgetPreview(for:)를 VoiceOver에서 숨기고 .accessibilityElement(children: .ignore)와 .accessibilityLabel("\(widget.kind.title), \(widget.family.title)")로 행 전체를 접근성 요소로 만든 부분에 대해 접근성 힌트와 버튼 트레잇을 추가하세요: 해당 뷰(해당 버튼 또는 래핑된 컨테이너)에 .accessibilityHint("위젯 추가 가이드를 표시합니다")를 설정하고 .accessibilityAddTraits(.isButton)를 호출하여 동작 힌트를 제공하고 이 요소가 버튼임을 명확히 표시하도록 수정합니다; 참조 지점은 widgetPreview(for:), .accessibilityElement(children: .ignore), .accessibilityLabel(...)입니다.Projects/Feature/AllergySettingFeature/Sources/AllergySettingView.swift (2)
43-65: 하단 저장 버튼 뷰 중복을 추출하는 것이 좋겠습니다.Line 45-53과 Line 56-64의 버튼 내용이 동일해 유지보수 시 드리프트가 생기기 쉽습니다. 공통 뷰로 추출하면 안전합니다.
변경 예시
+ `@ViewBuilder` + private var saveButton: some View { + if viewStore.allergyDidTap { + TWButton(title: "저장") { + viewStore.send(.saveButtonDidTap) + } + .padding(.horizontal, 16) + .padding(.bottom, 8) + } + } + public var body: some View { let scrollView = ScrollView { ... } if `#available`(iOS 26.0, *) { scrollView .safeAreaBar(edge: .bottom) { - if viewStore.allergyDidTap { - TWButton(title: "저장") { - viewStore.send(.saveButtonDidTap) - } - .padding(.horizontal, 16) - .padding(.bottom, 8) - } + saveButton } } else { scrollView .safeAreaInset(edge: .bottom) { - if viewStore.allergyDidTap { - TWButton(title: "저장") { - viewStore.send(.saveButtonDidTap) - } - .padding(.horizontal, 16) - .padding(.bottom, 8) - } + saveButton } } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Projects/Feature/AllergySettingFeature/Sources/AllergySettingView.swift` around lines 43 - 65, Extract the duplicated bottom save-button view into a single reusable view (e.g., a computed var or private func like saveButtonView()) that returns the TWButton wrapped with the padding and the conditional based on viewStore.allergyDidTap; then replace both uses of scrollView.safeAreaBar(...) and scrollView.safeAreaInset(...) so they call the same saveButtonView() inside their respective safeAreaBar/safeAreaInset closures (keep the iOS-availability branching for safeAreaBar vs safeAreaInset but move the TWButton creation and padding into the shared saveButtonView to avoid duplication).
24-27:allCases중복 조회와 fallback 기본값을 정리해 주세요.Line 24-27은 동일 소스를 두 번 조회하고, 불필요한 기본값(
.turbulence)으로 오류를 숨길 수 있습니다. 로컬 배열을 한 번만 만들고 직접 인덱싱하는 쪽이 안전하고 읽기 쉽습니다.변경 예시
- LazyVGrid(columns: columns, spacing: 8) { - ForEach(AllergyType.allCases.indices, id: \.self) { index in - let allergy = AllergyType.allCases[safe: index] ?? .turbulence + let allergies = AllergyType.allCases + LazyVGrid(columns: columns, spacing: 8) { + ForEach(allergies.indices, id: \.self) { index in + let allergy = allergies[index] allergyColumnView(index: index, allergy: allergy) .onTapGesture {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Projects/Feature/AllergySettingFeature/Sources/AllergySettingView.swift` around lines 24 - 27, Replace the duplicate AllergyType.allCases lookups and the unsafe fallback by capturing the cases once into a local constant (e.g., let allCases = AllergyType.allCases) and iterate that collection; inside the ForEach use direct indexing into that local array (or iterate the array elements themselves) to produce the allergy variable passed to allergyColumnView(index: index, allergy: allergy) so you no longer call AllergyType.allCases twice or rely on the .turbulence fallback.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@Projects/UserInterface/DesignSystem/Sources/TWTextField/TWTextField.swift`:
- Around line 63-65: The withAnimation call currently uses reduceMotion ? .none
: nil which results in no animation in both branches; change it to pass a real
animation when reduceMotion is false (for example withAnimation(reduceMotion ?
nil : .default) { text = "" }) or explicitly branch (if reduceMotion {
withAnimation(nil) { text = "" } } else { withAnimation { text = "" } }) so that
reduceMotion, withAnimation and text in TWTextField.swift behave correctly.
---
Nitpick comments:
In `@Projects/Feature/AddWidgetFeature/Sources/AddWidgetView.swift`:
- Around line 179-181: Replace the DispatchQueue.main.asyncAfter block with a
Task-based delay to tie the work to structured concurrency and the view
lifecycle: in the code using isGuideFocused (the `@AccessibilityFocusState`
binding), remove DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
isGuideFocused = true } and instead create a Task that awaits
Task.sleep(nanoseconds: 500_000_000) and then sets isGuideFocused on the main
actor (e.g., await MainActor.run { isGuideFocused = true }) so the work is
cancellable when the view disappears.
- Around line 146-147: Text(currentGuideText) already exposes its string to
VoiceOver, so calling .accessibilityLabel(currentGuideText) duplicates the
label; remove the redundant modifier on the view that chains
.accessibilityFocused($isGuideFocused) and .accessibilityLabel(currentGuideText)
(or replace the label argument with a distinct, intentional string only if you
need a different spoken value) so keep .accessibilityFocused($isGuideFocused)
and either delete .accessibilityLabel(currentGuideText) or supply a unique
accessibility label.
- Around line 64-87: 현재 widgetPreview(for:)를 VoiceOver에서 숨기고
.accessibilityElement(children: .ignore)와
.accessibilityLabel("\(widget.kind.title), \(widget.family.title)")로 행 전체를 접근성
요소로 만든 부분에 대해 접근성 힌트와 버튼 트레잇을 추가하세요: 해당 뷰(해당 버튼 또는 래핑된 컨테이너)에
.accessibilityHint("위젯 추가 가이드를 표시합니다")를 설정하고 .accessibilityAddTraits(.isButton)를
호출하여 동작 힌트를 제공하고 이 요소가 버튼임을 명확히 표시하도록 수정합니다; 참조 지점은 widgetPreview(for:),
.accessibilityElement(children: .ignore), .accessibilityLabel(...)입니다.
In `@Projects/Feature/AllergySettingFeature/Sources/AllergySettingView.swift`:
- Around line 43-65: Extract the duplicated bottom save-button view into a
single reusable view (e.g., a computed var or private func like
saveButtonView()) that returns the TWButton wrapped with the padding and the
conditional based on viewStore.allergyDidTap; then replace both uses of
scrollView.safeAreaBar(...) and scrollView.safeAreaInset(...) so they call the
same saveButtonView() inside their respective safeAreaBar/safeAreaInset closures
(keep the iOS-availability branching for safeAreaBar vs safeAreaInset but move
the TWButton creation and padding into the shared saveButtonView to avoid
duplication).
- Around line 24-27: Replace the duplicate AllergyType.allCases lookups and the
unsafe fallback by capturing the cases once into a local constant (e.g., let
allCases = AllergyType.allCases) and iterate that collection; inside the ForEach
use direct indexing into that local array (or iterate the array elements
themselves) to produce the allergy variable passed to allergyColumnView(index:
index, allergy: allergy) so you no longer call AllergyType.allCases twice or
rely on the .turbulence fallback.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 84f0b5d7-3d2e-438b-91eb-82cca951d1e2
📒 Files selected for processing (6)
Projects/Feature/AddWidgetFeature/Sources/AddWidgetView.swiftProjects/Feature/AllergySettingFeature/Sources/AllergySettingView.swiftProjects/Feature/SchoolSettingFeature/Sources/SchoolSettingView.swiftProjects/UserInterface/DesignSystem/Sources/TWButton/TWButtonStyle.swiftProjects/UserInterface/DesignSystem/Sources/TWFont/Font+tw.swiftProjects/UserInterface/DesignSystem/Sources/TWTextField/TWTextField.swift
✅ Files skipped from review due to trivial changes (2)
- Projects/UserInterface/DesignSystem/Sources/TWButton/TWButtonStyle.swift
- Projects/UserInterface/DesignSystem/Sources/TWFont/Font+tw.swift
🚧 Files skipped from review as they are similar to previous changes (1)
- Projects/Feature/SchoolSettingFeature/Sources/SchoolSettingView.swift
💡 개요
Accessibility 추가 지원
Summary by CodeRabbit
새로운 기능
기능 개선
디자인