mycode/myApp/HaruDanim/IOS/Views/SymbolPickerView.swift
songyc macbook 7d17917609 feat(main): add favorites quick-access row to Home tab
행동이 많아질수록 모음 탭이 복잡해진다는 실사용 피드백에 대한 1차 대응.
22개 시드로 iPhone·iPad·맥 4가지 레이아웃을 직접 시뮬레이션해 대안 A~F를
검토했고, 가장 저위험인 A(즐겨찾기 재사용)만 이번에 구현했다.

- Action.isFavorite(행동 탭과 동일 필드)를 켠 행동을 모음 탭 상단에
  가로 스크롤 한 줄로 노출 (전체 그리드에서는 제외하지 않음 — 숨기는
  방식은 검토 후 기각)
- 롱프레스 메뉴에 즐겨찾기 지정·해제 버튼 추가, 전체 그리드·즐겨찾기
  줄이 메뉴 구성을 공유하도록 리팩터
- 도움말에 새 주제 추가 + 관련 en/ja 번역, CLAUDE.md §6.1 문서화
- 감사 겸 아이콘 전용 버튼 접근성 라벨 누락 3건 수정(일기 할 일 체크박스,
  아이콘 선택기, 꼬리표 색 프리셋) — §15 컨벤션 위반이라 저위험 즉시 수정

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014CkLDi8Lr21vGqo4SjZaTp
2026-07-21 17:51:53 +09:00

96 lines
3.5 KiB
Swift

//
// SymbolPickerView.swift
// Haru_Danim
//
// SF Symbols ( + )
//
import SwiftUI
struct SymbolPickerView: View {
@Environment(\.dismiss) private var dismiss
@Binding var selection: String
@State private var searchText = ""
@State private var category: String
init(selection: Binding<String>) {
_selection = selection
_category = State(initialValue: SymbolCatalog.categories.first?.name ?? "")
}
private var symbols: [String] {
let trimmed = searchText.trimmingCharacters(in: .whitespaces).lowercased()
if !trimmed.isEmpty {
return SymbolCatalog.allSymbols.filter { $0.contains(trimmed) }
}
return SymbolCatalog.categories.first { $0.name == category }?.symbols ?? []
}
var body: some View {
NavigationStack {
VStack(spacing: 0) {
if searchText.isEmpty {
categoryBar
}
ScrollView {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 52), spacing: 10)], spacing: 10) {
ForEach(symbols, id: \.self) { symbol in
Button {
selection = symbol
dismiss()
} label: {
Image(systemName: symbol)
.font(.system(size: 22))
.foregroundStyle(symbol == selection ? Color.white : AppTheme.green)
.frame(width: 52, height: 52)
.background(
symbol == selection ? AppTheme.green : AppTheme.surface,
in: RoundedRectangle(cornerRadius: 12, style: .continuous)
)
}
.buttonStyle(.plain)
.accessibilityLabel(Text(verbatim: symbol))
}
}
.padding()
}
}
.background(AppTheme.background)
.navigationTitle("아이콘 선택")
.navigationBarTitleDisplayMode(.inline)
.searchable(text: $searchText, prompt: "심볼 이름 검색 (영문)")
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("닫기") { dismiss() }
}
}
}
}
private var categoryBar: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 8) {
ForEach(SymbolCatalog.categories) { cat in
Button {
category = cat.name
} label: {
Text(cat.name)
.font(.subheadline.weight(category == cat.name ? .semibold : .regular))
.padding(.horizontal, 14)
.padding(.vertical, 7)
.background(
category == cat.name ? AppTheme.green : AppTheme.surface,
in: Capsule()
)
.foregroundStyle(category == cat.name ? Color.white : Color.primary)
}
.buttonStyle(.plain)
}
}
.padding(.horizontal)
.padding(.vertical, 8)
}
}
}