-
Notifications
You must be signed in to change notification settings - Fork 1
Accessibility 추가 지원 #214
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Accessibility 추가 지원 #214
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -6,6 +6,7 @@ import NoticeFeature | |||
| import SettingsFeature | ||||
| import SwiftUI | ||||
| import TimeTableFeature | ||||
| import UIKit | ||||
| import TipKit | ||||
| import TWLog | ||||
|
|
||||
|
|
@@ -14,6 +15,7 @@ public struct MainView: View { | |||
| @ObservedObject var viewStore: ViewStoreOf<MainCore> | ||||
| @Environment(\.openURL) var openURL | ||||
| @Environment(\.calendar) var calendar | ||||
| @Environment(\.accessibilityReduceMotion) var reduceMotion | ||||
| @Dependency(\.userDefaultsClient) var userDefaultsClient | ||||
|
|
||||
| public init(store: StoreOf<MainCore>) { | ||||
|
|
@@ -28,6 +30,7 @@ public struct MainView: View { | |||
| .padding(.horizontal, 16) | ||||
| .padding(.vertical, 12) | ||||
| .accessibilityElement(children: .combine) | ||||
| .accessibilityAddTraits(.isStaticText) | ||||
| .accessibilityLabel({ | ||||
| let school: String = viewStore.school | ||||
| let grade: String = viewStore.grade | ||||
|
|
@@ -47,8 +50,6 @@ public struct MainView: View { | |||
| items: ["급식", "시간표"] | ||||
| ) | ||||
| .padding(.top, 32) | ||||
| .accessibilityLabel("메뉴 탭") | ||||
| .accessibilityHint("급식과 시간표 중 원하는 메뉴를 선택할 수 있습니다.") | ||||
|
|
||||
| ZStack(alignment: .bottomTrailing) { | ||||
| TabView( | ||||
|
|
@@ -86,18 +87,42 @@ public struct MainView: View { | |||
| Color.backgroundSecondary | ||||
| .ignoresSafeArea() | ||||
| } | ||||
| .onChange(of: viewStore.currentTab) { newTab in | ||||
| let tabName = newTab == 0 ? "급식" : "시간표" | ||||
| UIAccessibility.post( | ||||
| notification: .announcement, | ||||
| argument: "\(tabName) 탭" | ||||
| ) | ||||
| } | ||||
|
|
||||
| if viewStore.isShowingReviewToast { | ||||
| ReviewToast { | ||||
| viewStore.send(.requestReview) | ||||
| TWLog.event(ClickReviewEventLog()) | ||||
| } | ||||
| ReviewToast( | ||||
| onTap: { | ||||
| viewStore.send(.requestReview) | ||||
| TWLog.event(ClickReviewEventLog()) | ||||
| }, | ||||
| onDismiss: { | ||||
| viewStore.send(.hideReviewToast, animation: .default) | ||||
| } | ||||
| ) | ||||
|
Comment on lines
+99
to
+107
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 리뷰 토스트 dismiss 경로가 reduceMotion 설정을 완전히 따르지 않습니다. Line 105(그리고 동일한 Line 127)에서 🔧 제안 수정 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 |
||||
| .frame(maxWidth: .infinity) | ||||
| .padding(.horizontal, 16) | ||||
| .padding(.bottom, 24) | ||||
| .animation(.default, value: viewStore.isShowingReviewToast) | ||||
| .transition(.move(edge: .bottom).combined(with: .opacity)) | ||||
| .animation( | ||||
| reduceMotion ? .none : .default, | ||||
| value: viewStore.isShowingReviewToast | ||||
| ) | ||||
| .transition( | ||||
| reduceMotion | ||||
| ? .opacity | ||||
| : .move(edge: .bottom).combined(with: .opacity) | ||||
| ) | ||||
| .onAppear { | ||||
| UIAccessibility.post( | ||||
| notification: .announcement, | ||||
| argument: "앱 리뷰 요청이 표시되었습니다" | ||||
| ) | ||||
| guard !UIAccessibility.isVoiceOverRunning else { return } | ||||
| DispatchQueue.main.asyncAfter(deadline: .now() + 7.5) { | ||||
| viewStore.send(.hideReviewToast, animation: .default) | ||||
| } | ||||
|
|
@@ -173,7 +198,7 @@ public struct MainView: View { | |||
| } | ||||
| } | ||||
| .accessibilityLabel("날짜 선택") | ||||
| .accessibilityHint("클릭하여 날짜를 선택할 수 있습니다") | ||||
| .accessibilityRemoveTraits(.isButton) | ||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 메뉴 트리거에서 버튼 트레이트 제거는 접근성 인지성을 떨어뜨릴 수 있습니다. Line 201의 🔧 제안 수정- .accessibilityRemoveTraits(.isButton)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||
| } | ||||
|
|
||||
| ToolbarItemGroup(placement: .topBarTrailing) { | ||||
|
|
||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -153,8 +153,8 @@ public struct WeeklyMealView: View { | |
| let calText: String = String(format: "%.1f", subMeal.cal) | ||
| let calLabel: String = "\(calText) Kcal" | ||
| let titleText: String = relativeTitle(for: dayMeal.date, mealType: type) | ||
| let accessibilityText: String = "\(titleText) \(calText) 칼로리" | ||
| let mealTexts: [String] = subMeal.meals.map { mealDisplay(meal: $0) } | ||
| let accessibilityText: String = "\(titleText) \(calText) 칼로리. \(mealTexts.joined(separator: ", "))" | ||
| let dateText: String = "\(dayMeal.date.formatted(.dateTime.month().day().weekday(.wide))) \(type.display)" | ||
| let joinedMeals: String = mealTexts.joined(separator: "\n") | ||
| let shareText: String = "\(dateText)\n\(joinedMeals)" | ||
|
|
@@ -213,6 +213,20 @@ public struct WeeklyMealView: View { | |
| 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()) | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
+220
to
+229
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 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의 제안 수정안- 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 |
||
| .contextMenu { | ||
| Button { | ||
| UIPasteboard.general.string = shareText | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,7 @@ public struct SchoolSettingView: View { | |
| private let isNavigationPushed: Bool | ||
| @FocusState private var focusField: FocusField? | ||
| @Environment(\.dismiss) var dismiss | ||
| @Environment(\.accessibilityReduceMotion) private var reduceMotion | ||
|
|
||
| public init( | ||
| store: StoreOf<SchoolSettingCore>, | ||
|
|
@@ -46,9 +47,9 @@ public struct SchoolSettingView: View { | |
| schoolSearchResults(viewStore: viewStore) | ||
| } | ||
| } | ||
| .animation(.default, value: viewStore.grade) | ||
| .animation(.default, value: viewStore.class) | ||
| .animation(.default, value: viewStore.school) | ||
| .animation(reduceMotion ? .none : .default, value: viewStore.grade) | ||
| .animation(reduceMotion ? .none : .default, value: viewStore.class) | ||
| .animation(reduceMotion ? .none : .default, value: viewStore.school) | ||
|
Comment on lines
+50
to
+52
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 감소된 모션 처리가 일부 경로에서만 적용됩니다. Line 50~52는 잘 반영됐지만, Line 56/71/131/222/245의 🔧 제안 수정 .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 |
||
| .padding(.horizontal, 16) | ||
| .padding(.top, 24) | ||
| .onChange(of: focusField) { newValue in | ||
|
|
@@ -124,14 +125,16 @@ public struct SchoolSettingView: View { | |
| ) | ||
| ) | ||
| .disabled(true) | ||
| .accessibilityLabel("학과 선택") | ||
| .accessibilityHint("학과를 선택하려면 두 번 탭하세요") | ||
| } | ||
| .padding(.bottom, 16) | ||
| .onTapGesture { | ||
| viewStore.send(.majorTextFieldDidTap, animation: .default) | ||
| focusField = nil | ||
| } | ||
| .accessibilityElement(children: .ignore) | ||
| .accessibilityLabel(viewStore.major.isEmpty ? "학과 선택" : "학과: \(viewStore.major)") | ||
| .accessibilityHint("탭하면 학과 선택 시트가 열립니다") | ||
| .accessibilityAddTraits(.isButton) | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -205,21 +208,19 @@ public struct SchoolSettingView: View { | |
| ScrollView { | ||
| LazyVStack(spacing: 16) { | ||
| ForEach(viewStore.schoolList, id: \.schoolCode) { school in | ||
| HStack { | ||
| schoolRowView(school: school) | ||
|
|
||
| Spacer() | ||
| } | ||
| .frame(maxWidth: .infinity) | ||
| .background { | ||
| Color.backgroundMain | ||
| } | ||
| .contentShape(.rect) | ||
| .onTapGesture { | ||
| Button { | ||
| viewStore.send(.schoolRowDidSelect(school), animation: .default) | ||
| focusField = .grade | ||
| } label: { | ||
| HStack { | ||
| schoolRowView(school: school) | ||
|
|
||
| Spacer() | ||
| } | ||
| .frame(maxWidth: .infinity, minHeight: 44) | ||
| .contentShape(.rect) | ||
| } | ||
| .accessibilityElement(children: .combine) | ||
| .buttonStyle(.plain) | ||
| .accessibilityLabel("\(school.name) \(school.location)") | ||
| .accessibilityHint("이 학교를 선택하려면 두 번 탭하세요") | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#available(iOS 26.0, *)확인은 존재하지 않는 iOS 버전을 대상으로 하고 있어 혼란을 줄 수 있습니다.glassEffect는 visionOS에서 사용 가능하므로,#if os(visionOS)와 같은 컴파일러 지시문을 사용하거나, 특정 iOS 기능을 확인하는 것이라면 실제 버전을 사용해야 합니다. 예를 들어iOS 17.0이나visionOS 1.0을 확인하는 것이 더 명확할 것 같습니다. 이 코드가 의도한 바가 무엇인지 확인하고 수정하는 것을 권장합니다.