Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:49:07 +08:00
commit 37931844a6
12 changed files with 2391 additions and 0 deletions

View File

@@ -0,0 +1,208 @@
#!/bin/bash
# Apple HIG Designer - iOS Accessibility Audit
# Check iOS app accessibility compliance (VoiceOver, Dynamic Type, etc.)
set -e
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
MAGENTA='\033[0;35m'
NC='\033[0m'
PASS_COUNT=0
FAIL_COUNT=0
WARNING_COUNT=0
print_success() {
echo -e "${GREEN}✓ PASS${NC} $1"
((PASS_COUNT++))
}
print_error() {
echo -e "${RED}✗ FAIL${NC} $1"
((FAIL_COUNT++))
}
print_warning() {
echo -e "${YELLOW}⚠ WARN${NC} $1"
((WARNING_COUNT++))
}
print_info() {
echo -e "${BLUE} INFO${NC} $1"
}
print_section() {
echo ""
echo -e "${MAGENTA}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${MAGENTA}$1${NC}"
echo -e "${MAGENTA}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
}
echo ""
echo "╔════════════════════════════════════════════════════════════╗"
echo "║ ║"
echo "║ Apple HIG Designer - Accessibility Audit ║"
echo "║ ║"
echo "╚════════════════════════════════════════════════════════════╝"
echo ""
if [ -z "$1" ]; then
print_info "Usage: $0 <file.swift|directory>"
exit 1
fi
TARGET="$1"
if [ ! -e "$TARGET" ]; then
print_error "Target not found: $TARGET"
exit 1
fi
# VoiceOver Support
print_section "1. VOICEOVER SUPPORT"
check_voiceover() {
if grep -q 'Image(systemName:' "$1"; then
if grep -q '\.accessibilityLabel' "$1"; then
print_success "Images have accessibility labels"
else
print_error "Images missing accessibility labels"
echo " Fix: .accessibilityLabel(\"Description\")"
fi
fi
if grep -q '\.accessibilityHint' "$1"; then
print_success "Accessibility hints provided"
fi
if grep -q '\.accessibilityElement(children: .combine)' "$1"; then
print_success "Grouping accessibility elements"
fi
}
# Dynamic Type
print_section "2. DYNAMIC TYPE"
check_dynamic_type() {
if grep -q '\.font(.body)\|\.font(.headline)\|\.font(.title)' "$1"; then
print_success "Using system text styles (Dynamic Type supported)"
else
print_error "Not using system text styles"
echo " Fix: Use .font(.body) instead of .font(.system(size: 17))"
fi
if grep -q '\.font(.custom(' "$1"; then
if grep -q 'relativeTo:' "$1"; then
print_success "Custom fonts support Dynamic Type"
else
print_error "Custom fonts don't support Dynamic Type"
echo " Fix: .font(.custom(\"Font\", size: 17, relativeTo: .body))"
fi
fi
}
# Color Contrast
print_section "3. COLOR CONTRAST"
check_color_contrast() {
if grep -q 'Color(.label)\|Color(.secondaryLabel)' "$1"; then
print_success "Using semantic text colors (good contrast)"
else
print_warning "Verify color contrast ratios (4.5:1 minimum)"
fi
if grep -q '@Environment(.*colorSchemeContrast)' "$1"; then
print_success "Supporting Increase Contrast mode"
else
print_warning "Consider supporting Increase Contrast"
echo " Tip: @Environment(\\.colorSchemeContrast) var contrast"
fi
}
# Reduce Motion
print_section "4. REDUCE MOTION"
check_reduce_motion() {
if grep -q '@Environment(.*accessibilityReduceMotion)' "$1"; then
print_success "Respecting Reduce Motion preference"
else
if grep -q 'withAnimation\|\.animation' "$1"; then
print_error "Animations present but not respecting Reduce Motion"
echo " Fix: @Environment(\\.accessibilityReduceMotion) var reduceMotion"
fi
fi
}
# Touch Targets
print_section "5. TOUCH TARGETS"
check_touch_targets() {
if grep -q '\.frame.*minHeight.*44\|height.*44' "$1"; then
print_success "Minimum touch target (44pt) specified"
else
if grep -q 'Button\|.onTapGesture' "$1"; then
print_error "Touch targets may be too small"
echo " Fix: .frame(minWidth: 44, minHeight: 44)"
fi
fi
}
# Run checks
if [ -f "$TARGET" ]; then
if [[ "$TARGET" == *.swift ]]; then
check_voiceover "$TARGET"
check_dynamic_type "$TARGET"
check_color_contrast "$TARGET"
check_reduce_motion "$TARGET"
check_touch_targets "$TARGET"
fi
elif [ -d "$TARGET" ]; then
swift_files=$(find "$TARGET" -name "*.swift" 2>/dev/null)
for file in $swift_files; do
print_info "Checking: $file"
check_voiceover "$file"
check_dynamic_type "$file"
check_color_contrast "$file"
check_reduce_motion "$file"
check_touch_targets "$file"
echo ""
done
fi
# Summary
echo ""
echo "╔════════════════════════════════════════════════════════════╗"
echo "║ Accessibility Summary ║"
echo "╚════════════════════════════════════════════════════════════╝"
echo ""
echo -e "${GREEN}✓ Passed: $PASS_COUNT${NC}"
echo -e "${RED}✗ Failed: $FAIL_COUNT${NC}"
echo -e "${YELLOW}⚠ Warnings: $WARNING_COUNT${NC}"
echo ""
TOTAL=$((PASS_COUNT + FAIL_COUNT))
if [ $TOTAL -gt 0 ]; then
SCORE=$(( (PASS_COUNT * 100) / TOTAL ))
echo "Accessibility Score: $SCORE%"
echo ""
fi
echo ""
print_info "Testing Recommendations:"
echo " 1. Test with VoiceOver enabled (Settings > Accessibility > VoiceOver)"
echo " 2. Test with largest Dynamic Type size"
echo " 3. Test with Increase Contrast enabled"
echo " 4. Test with Reduce Motion enabled"
echo " 5. Test with Reduce Transparency enabled"
echo " 6. Use Accessibility Inspector in Xcode"
echo ""
print_info "Resources:"
echo " - Accessibility Inspector: Xcode > Open Developer Tool"
echo " - Apple Accessibility: https://www.apple.com/accessibility/"
echo ""
[ $FAIL_COUNT -gt 0 ] && exit 1 || exit 0

View File

@@ -0,0 +1,348 @@
#!/bin/bash
# Apple HIG Designer - iOS Component Generator
# Generate SwiftUI and UIKit components following Apple Human Interface Guidelines
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Helper functions
print_success() {
echo -e "${GREEN}$1${NC}"
}
print_error() {
echo -e "${RED}$1${NC}"
}
print_info() {
echo -e "${BLUE} $1${NC}"
}
print_warning() {
echo -e "${YELLOW}$1${NC}"
}
prompt_input() {
local prompt="$1"
local var_name="$2"
local required="${3:-false}"
while true; do
echo -e "${BLUE}${prompt}${NC}"
read -r input
if [ -z "$input" ] && [ "$required" = true ]; then
print_error "This field is required."
continue
fi
eval "$var_name='$input'"
break
done
}
prompt_select() {
local prompt="$1"
local var_name="$2"
shift 2
local options=("$@")
echo -e "${BLUE}${prompt}${NC}"
PS3="Select (1-${#options[@]}): "
select opt in "${options[@]}"; do
if [ -n "$opt" ]; then
eval "$var_name='$opt'"
break
else
print_error "Invalid selection. Try again."
fi
done
}
# Banner
echo ""
echo "╔════════════════════════════════════════════════════════════╗"
echo "║ ║"
echo "║ Apple HIG Designer - Component Generator ║"
echo "║ ║"
echo "╚════════════════════════════════════════════════════════════╝"
echo ""
# Step 1: Framework
print_info "Step 1/6: Framework"
prompt_select "Which framework?" FRAMEWORK \
"SwiftUI" \
"UIKit"
# Step 2: Component Type
print_info "Step 2/6: Component Type"
prompt_select "What type of component?" COMPONENT_TYPE \
"Button" \
"List/TableView" \
"Card" \
"Sheet/Modal" \
"Form" \
"NavigationView" \
"TabView" \
"Custom"
# Step 3: Component Name
print_info "Step 3/6: Component Name"
prompt_input "Component name (e.g., UserProfileView):" COMPONENT_NAME true
# Step 4: Features
print_info "Step 4/6: Features (comma-separated)"
echo -e "${BLUE}Select features to include:${NC}"
echo " - accessibility (VoiceOver, Dynamic Type)"
echo " - darkmode (Semantic colors)"
echo " - animations (Standard iOS animations)"
echo " - haptics (Haptic feedback)"
read -r FEATURES
# Step 5: Platform
print_info "Step 5/6: Platform"
prompt_select "Target platform?" PLATFORM \
"iOS" \
"iOS + iPadOS" \
"iOS + watchOS" \
"All (iOS, iPadOS, macOS, watchOS)"
# Step 6: Output Directory
print_info "Step 6/6: Output Location"
prompt_input "Output directory (default: ./Components):" OUTPUT_DIR
OUTPUT_DIR=${OUTPUT_DIR:-"./Components"}
# Create output directory
mkdir -p "$OUTPUT_DIR"
# Generate based on framework
case $FRAMEWORK in
"SwiftUI")
generate_swiftui_component
;;
"UIKit")
generate_uikit_component
;;
esac
generate_swiftui_component() {
local file_path="$OUTPUT_DIR/$COMPONENT_NAME.swift"
cat > "$file_path" << 'EOF'
import SwiftUI
/// COMPONENT_NAME
///
/// Description of what this component does
/// Follows Apple Human Interface Guidelines
struct COMPONENT_NAME: View {
PROPERTIES
var body: some View {
COMPONENT_BODY
}
}
PREVIEW
EOF
# Add properties based on component type
case $COMPONENT_TYPE in
"Button")
sed -i 's/PROPERTIES/\/\/ Button properties\n let title: String\n let action: () -> Void/' "$file_path"
sed -i 's/COMPONENT_BODY/Button(action: action) {\n Text(title)\n }\n .buttonStyle(.borderedProminent)\n ACCESSIBILITY_MODIFIERS/' "$file_path"
;;
"List/TableView")
sed -i 's/PROPERTIES/\/\/ List properties\n let items: [String]/' "$file_path"
sed -i 's/COMPONENT_BODY/List(items, id: \\.self) { item in\n Text(item)\n }\n .listStyle(.insetGrouped)\n ACCESSIBILITY_MODIFIERS/' "$file_path"
;;
"Card")
sed -i 's/PROPERTIES/\/\/ Card properties\n let title: String\n let description: String/' "$file_path"
sed -i 's/COMPONENT_BODY/VStack(alignment: .leading, spacing: 12) {\n Text(title)\n .font(.headline)\n \n Text(description)\n .font(.subheadline)\n .foregroundColor(.secondary)\n }\n .padding()\n .background(Color(.systemBackground))\n .cornerRadius(12)\n .shadow(color: .black.opacity(0.1), radius: 8, x: 0, y: 4)\n ACCESSIBILITY_MODIFIERS/' "$file_path"
;;
"Sheet/Modal")
sed -i 's/PROPERTIES/@State private var isPresented = false/' "$file_path"
sed -i 's/COMPONENT_BODY/Button("Show Sheet") {\n isPresented = true\n }\n .sheet(isPresented: $isPresented) {\n NavigationStack {\n Text("Sheet Content")\n .navigationTitle("Title")\n .navigationBarTitleDisplayMode(.inline)\n .toolbar {\n ToolbarItem(placement: .cancellationAction) {\n Button("Cancel") {\n isPresented = false\n }\n }\n }\n }\n .presentationDetents([.medium, .large])\n }/' "$file_path"
;;
"NavigationView")
sed -i 's/PROPERTIES/\/\/ Navigation properties\n @State private var path = NavigationPath()/' "$file_path"
sed -i 's/COMPONENT_BODY/NavigationStack(path: $path) {\n List {\n NavigationLink("Item 1", value: "Detail 1")\n NavigationLink("Item 2", value: "Detail 2")\n }\n .navigationTitle("Title")\n .navigationBarTitleDisplayMode(.large)\n .navigationDestination(for: String.self) { value in\n Text(value)\n }\n }/' "$file_path"
;;
"TabView")
sed -i 's/PROPERTIES/@State private var selectedTab = 0/' "$file_path"
sed -i 's/COMPONENT_BODY/TabView(selection: $selectedTab) {\n Text("Home")\n .tabItem {\n Label("Home", systemImage: "house")\n }\n .tag(0)\n \n Text("Search")\n .tabItem {\n Label("Search", systemImage: "magnifyingglass")\n }\n .tag(1)\n \n Text("Profile")\n .tabItem {\n Label("Profile", systemImage: "person")\n }\n .tag(2)\n }/' "$file_path"
;;
*)
sed -i 's/PROPERTIES/\/\/ Component properties/' "$file_path"
sed -i 's/COMPONENT_BODY/Text("Custom Component")\n .font(.body)\n ACCESSIBILITY_MODIFIERS/' "$file_path"
;;
esac
# Add accessibility modifiers if requested
if [[ $FEATURES == *"accessibility"* ]]; then
sed -i 's/ACCESSIBILITY_MODIFIERS/.accessibilityLabel("Component label")\n .accessibilityHint("Component hint")/' "$file_path"
else
sed -i 's/ACCESSIBILITY_MODIFIERS//' "$file_path"
fi
# Add preview
sed -i "s/PREVIEW/#Preview {\n COMPONENT_NAME()\n}/" "$file_path"
# Replace component name
sed -i "s/COMPONENT_NAME/$COMPONENT_NAME/g" "$file_path"
print_success "Created SwiftUI component: $file_path"
# Generate test file if needed
if [[ $FEATURES == *"testing"* ]]; then
generate_test_file_swiftui
fi
}
generate_uikit_component() {
local file_path="$OUTPUT_DIR/$COMPONENT_NAME.swift"
cat > "$file_path" << 'EOF'
import UIKit
/// COMPONENT_NAME
///
/// Description of what this component does
/// Follows Apple Human Interface Guidelines
class COMPONENT_NAME: UIView {
// MARK: - Properties
PROPERTIES
// MARK: - Initialization
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
// MARK: - Setup
private func setupView() {
SETUP_CODE
setupAccessibility()
}
private func setupAccessibility() {
ACCESSIBILITY_SETUP
}
// MARK: - Layout
override func layoutSubviews() {
super.layoutSubviews()
// Layout code here
}
}
EOF
# Add properties based on component type
case $COMPONENT_TYPE in
"Button")
sed -i 's/PROPERTIES/private let button: UIButton = {\n let button = UIButton(type: .system)\n button.translatesAutoresizingMaskIntoConstraints = false\n button.configuration = .filled()\n return button\n }()/' "$file_path"
sed -i 's/SETUP_CODE/addSubview(button)\n \n NSLayoutConstraint.activate([\n button.centerXAnchor.constraint(equalTo: centerXAnchor),\n button.centerYAnchor.constraint(equalTo: centerYAnchor),\n button.heightAnchor.constraint(greaterThanOrEqualToConstant: 44)\n ])/' "$file_path"
;;
"List/TableView")
sed -i 's/PROPERTIES/private let tableView: UITableView = {\n let table = UITableView(frame: .zero, style: .insetGrouped)\n table.translatesAutoresizingMaskIntoConstraints = false\n return table\n }()\n \n private var items: [String] = []/' "$file_path"
sed -i 's/SETUP_CODE/addSubview(tableView)\n tableView.delegate = self\n tableView.dataSource = self\n \n NSLayoutConstraint.activate([\n tableView.topAnchor.constraint(equalTo: topAnchor),\n tableView.leadingAnchor.constraint(equalTo: leadingAnchor),\n tableView.trailingAnchor.constraint(equalTo: trailingAnchor),\n tableView.bottomAnchor.constraint(equalTo: bottomAnchor)\n ])/' "$file_path"
;;
*)
sed -i 's/PROPERTIES/\/\/ Add component properties here/' "$file_path"
sed -i 's/SETUP_CODE/\/\/ Setup UI components/' "$file_path"
;;
esac
# Add accessibility setup
if [[ $FEATURES == *"accessibility"* ]]; then
sed -i 's/ACCESSIBILITY_SETUP/isAccessibilityElement = true\n accessibilityLabel = "Component label"\n accessibilityHint = "Component hint"\n accessibilityTraits = .button/' "$file_path"
else
sed -i 's/ACCESSIBILITY_SETUP/\/\/ Configure accessibility/' "$file_path"
fi
# Replace component name
sed -i "s/COMPONENT_NAME/$COMPONENT_NAME/g" "$file_path"
print_success "Created UIKit component: $file_path"
}
generate_test_file_swiftui() {
local test_file="$OUTPUT_DIR/${COMPONENT_NAME}Tests.swift"
cat > "$test_file" << 'EOF'
import XCTest
import SwiftUI
@testable import YourApp
final class COMPONENT_NAMETests: XCTestCase {
func testComponentRenders() {
let view = COMPONENT_NAME()
XCTAssertNotNil(view)
}
func testAccessibility() {
// Test VoiceOver labels
// Test Dynamic Type support
}
}
EOF
sed -i "s/COMPONENT_NAME/$COMPONENT_NAME/g" "$test_file"
print_success "Created test file: $test_file"
}
# Summary
echo ""
echo "╔════════════════════════════════════════════════════════════╗"
echo "║ Generation Complete ║"
echo "╚════════════════════════════════════════════════════════════╝"
echo ""
print_success "Component: $COMPONENT_NAME"
print_success "Framework: $FRAMEWORK"
print_success "Type: $COMPONENT_TYPE"
print_success "Platform: $PLATFORM"
print_success "Location: $OUTPUT_DIR"
echo ""
print_info "Files created:"
echo " - $COMPONENT_NAME.swift"
if [[ $FEATURES == *"testing"* ]]; then
echo " - ${COMPONENT_NAME}Tests.swift"
fi
echo ""
print_info "Apple HIG Guidelines Applied:"
echo " ✓ Minimum tap target: 44x44 points"
echo " ✓ System fonts (San Francisco)"
echo " ✓ Semantic colors (dark mode support)"
if [[ $FEATURES == *"accessibility"* ]]; then
echo " ✓ VoiceOver support"
echo " ✓ Dynamic Type support"
fi
if [[ $FEATURES == *"haptics"* ]]; then
echo " ✓ Haptic feedback"
fi
echo ""
print_info "Next steps:"
echo " 1. Review generated code"
echo " 2. Add component to your Xcode project"
echo " 3. Customize properties and logic"
echo " 4. Test with VoiceOver"
echo " 5. Test in light and dark mode"
echo " 6. Test with different Dynamic Type sizes"
echo ""

View File

@@ -0,0 +1,393 @@
#!/bin/bash
# Apple HIG Designer - Design Validation
# Validate Swift code against Apple Human Interface Guidelines
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
MAGENTA='\033[0;35m'
NC='\033[0m' # No Color
# Counters
PASS_COUNT=0
FAIL_COUNT=0
WARNING_COUNT=0
# Helper functions
print_success() {
echo -e "${GREEN}✓ PASS${NC} $1"
((PASS_COUNT++))
}
print_error() {
echo -e "${RED}✗ FAIL${NC} $1"
((FAIL_COUNT++))
}
print_warning() {
echo -e "${YELLOW}⚠ WARN${NC} $1"
((WARNING_COUNT++))
}
print_info() {
echo -e "${BLUE} INFO${NC} $1"
}
print_section() {
echo ""
echo -e "${MAGENTA}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${MAGENTA}$1${NC}"
echo -e "${MAGENTA}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
}
# Banner
echo ""
echo "╔════════════════════════════════════════════════════════════╗"
echo "║ ║"
echo "║ Apple HIG Designer - Design Validation ║"
echo "║ ║"
echo "╚════════════════════════════════════════════════════════════╝"
echo ""
# Get target
if [ -z "$1" ]; then
print_info "Usage: $0 <file.swift|directory>"
print_info "Example: $0 ContentView.swift"
print_info "Example: $0 Views/"
exit 1
fi
TARGET="$1"
# Check if target exists
if [ ! -e "$TARGET" ]; then
print_error "Target not found: $TARGET"
exit 1
fi
# Section 1: Typography
print_section "1. TYPOGRAPHY"
check_system_fonts() {
# Check for system font usage
if grep -q '\.font(' "$1"; then
if grep -q '\.font(.body)\|\.font(.title)\|\.font(.headline)' "$1"; then
print_success "Using system text styles (Dynamic Type)"
else
print_warning "Consider using system text styles (.body, .headline, etc.)"
fi
fi
# Check for hard-coded font sizes
if grep -q 'Font.system(size:' "$1"; then
print_warning "Hard-coded font sizes found - consider Dynamic Type"
echo " Tip: Use .font(.body) instead of .font(.system(size: 17))"
fi
}
# Section 2: Colors
print_section "2. COLORS & DARK MODE"
check_semantic_colors() {
# Check for semantic color usage
if grep -q 'Color(.label)\|Color(.systemBackground)\|Color(.secondary)' "$1"; then
print_success "Using semantic colors (dark mode support)"
else
if grep -q 'Color(' "$1"; then
print_warning "Consider using semantic colors for dark mode support"
echo " Use: Color(.label) instead of Color.black"
fi
fi
# Check for hard-coded colors
if grep -q '#[0-9A-Fa-f]\{6\}\|Color.black\|Color.white' "$1"; then
print_error "Hard-coded colors found - use semantic colors"
echo " Fix: Use Color(.label), Color(.systemBackground), etc."
fi
}
# Section 3: Accessibility
print_section "3. ACCESSIBILITY"
check_accessibility_labels() {
# Check for accessibility labels
if grep -q 'Image(systemName:' "$1"; then
if grep -q '\.accessibilityLabel' "$1"; then
print_success "SF Symbols have accessibility labels"
else
print_error "SF Symbols missing accessibility labels"
echo " Fix: Add .accessibilityLabel(\"Description\")"
fi
fi
# Check for VoiceOver support
if grep -q '\.accessibilityLabel\|\.accessibilityHint\|\.accessibilityValue' "$1"; then
print_success "VoiceOver support implemented"
else
print_warning "Consider adding VoiceOver support"
fi
}
# Section 4: Touch Targets
print_section "4. TOUCH TARGETS"
check_touch_targets() {
# Check for minimum tap targets
if grep -q '\.frame(.*height.*44\|minHeight.*44' "$1"; then
print_success "Minimum touch target size (44pt) specified"
else
if grep -q 'Button\|.onTapGesture' "$1"; then
print_warning "Verify touch targets are at least 44x44 points"
echo " Tip: .frame(minWidth: 44, minHeight: 44)"
fi
fi
}
# Section 5: Spacing
print_section "5. SPACING & LAYOUT"
check_spacing() {
# Check for 8pt grid usage
if grep -q '\.padding([0-9]*)\|\.spacing([0-9]*)' "$1"; then
# Extract padding values
paddings=$(grep -o '\.padding([0-9]*)' "$1" | grep -o '[0-9]*')
invalid=false
for pad in $paddings; do
if [ $((pad % 8)) -ne 0 ]; then
invalid=true
break
fi
done
if [ "$invalid" = false ]; then
print_success "Following 8pt grid system"
else
print_warning "Consider using 8pt grid (8, 16, 24, 32, etc.)"
fi
fi
# Check for safe area usage
if grep -q '\.ignoresSafeArea' "$1"; then
print_warning "Ignoring safe areas - ensure intentional"
echo " Tip: Respect safe areas for better device compatibility"
fi
}
# Section 6: Navigation
print_section "6. NAVIGATION"
check_navigation() {
# Check for navigation best practices
if grep -q 'NavigationStack\|NavigationView' "$1"; then
if grep -q '\.navigationTitle' "$1"; then
print_success "Navigation titles present"
else
print_error "NavigationStack missing .navigationTitle"
fi
# Check for navigation bar mode
if grep -q '\.navigationBarTitleDisplayMode(.large)' "$1"; then
print_success "Using large titles for top-level views"
fi
fi
# Check for TabView
if grep -q 'TabView' "$1"; then
# Count tabItems
tab_count=$(grep -c '\.tabItem' "$1")
if [ "$tab_count" -ge 3 ] && [ "$tab_count" -le 5 ]; then
print_success "TabView has appropriate number of tabs ($tab_count)"
elif [ "$tab_count" -gt 5 ]; then
print_error "Too many tabs ($tab_count) - maximum 5 recommended"
elif [ "$tab_count" -lt 3 ]; then
print_warning "Consider if TabView is appropriate for $tab_count tabs"
fi
fi
}
# Section 7: Buttons
print_section "7. BUTTONS & CONTROLS"
check_buttons() {
# Check for button styles
if grep -q 'Button(' "$1"; then
if grep -q '\.buttonStyle' "$1"; then
print_success "Using button styles"
# Check for button hierarchy
if grep -q '\.buttonStyle(.borderedProminent)' "$1"; then
prominent_count=$(grep -c '\.buttonStyle(.borderedProminent)' "$1")
if [ "$prominent_count" -eq 1 ]; then
print_success "Single prominent button (good hierarchy)"
else
print_warning "Multiple prominent buttons - ensure clear hierarchy"
fi
fi
else
print_warning "Consider using .buttonStyle for consistent appearance"
fi
# Check button labels
if grep -q 'Button("' "$1"; then
# Extract button texts
if grep -q 'Button("[A-Z]' "$1"; then
print_success "Button labels start with capital letters"
fi
fi
fi
}
# Section 8: Lists
print_section "8. LISTS & TABLES"
check_lists() {
# Check for list styles
if grep -q 'List' "$1"; then
if grep -q '\.listStyle' "$1"; then
if grep -q '\.listStyle(.insetGrouped)' "$1"; then
print_success "Using iOS standard .insetGrouped list style"
fi
else
print_warning "Consider specifying .listStyle(.insetGrouped)"
fi
fi
}
# Section 9: Animations
print_section "9. ANIMATIONS & MOTION"
check_animations() {
# Check for spring animations
if grep -q 'withAnimation' "$1"; then
if grep -q '\.spring()\|\.easeInOut' "$1"; then
print_success "Using iOS-style animations"
fi
fi
# Check for reduce motion
if grep -q '@Environment(.*accessibilityReduceMotion)' "$1"; then
print_success "Respecting Reduce Motion preference"
else
if grep -q 'withAnimation\|\.animation' "$1"; then
print_warning "Consider respecting Reduce Motion accessibility setting"
echo " Tip: @Environment(\\.accessibilityReduceMotion) var reduceMotion"
fi
fi
}
# Section 10: SF Symbols
print_section "10. SF SYMBOLS"
check_sf_symbols() {
# Check for SF Symbols usage
if grep -q 'Image(systemName:' "$1"; then
print_success "Using SF Symbols"
# Check for proper sizing
if grep -q '\.font(.title)\|\.imageScale' "$1"; then
print_success "SF Symbols are properly sized"
else
print_warning "Consider sizing SF Symbols with .font() or .imageScale()"
fi
fi
# Check for custom images that should be SF Symbols
common_icons="heart|star|person|home|settings|search|share"
if grep -E "Image\\(\"($common_icons)" "$1"; then
print_warning "Consider using SF Symbols instead of custom icons"
fi
}
# Run checks on files
if [ -f "$TARGET" ]; then
# Single file
if [[ "$TARGET" == *.swift ]]; then
check_system_fonts "$TARGET"
check_semantic_colors "$TARGET"
check_accessibility_labels "$TARGET"
check_touch_targets "$TARGET"
check_spacing "$TARGET"
check_navigation "$TARGET"
check_buttons "$TARGET"
check_lists "$TARGET"
check_animations "$TARGET"
check_sf_symbols "$TARGET"
else
print_error "File is not a Swift file: $TARGET"
exit 1
fi
elif [ -d "$TARGET" ]; then
# Directory - find Swift files
swift_files=$(find "$TARGET" -name "*.swift" 2>/dev/null)
if [ -z "$swift_files" ]; then
print_error "No Swift files found in $TARGET"
exit 1
fi
for file in $swift_files; do
print_info "Checking: $file"
check_system_fonts "$file"
check_semantic_colors "$file"
check_accessibility_labels "$file"
check_touch_targets "$file"
check_spacing "$file"
echo ""
done
fi
# Summary
echo ""
echo "╔════════════════════════════════════════════════════════════╗"
echo "║ Validation Summary ║"
echo "╚════════════════════════════════════════════════════════════╝"
echo ""
echo -e "${GREEN}✓ Passed: $PASS_COUNT${NC}"
echo -e "${RED}✗ Failed: $FAIL_COUNT${NC}"
echo -e "${YELLOW}⚠ Warnings: $WARNING_COUNT${NC}"
echo ""
# Calculate score
TOTAL=$((PASS_COUNT + FAIL_COUNT))
if [ $TOTAL -gt 0 ]; then
SCORE=$(( (PASS_COUNT * 100) / TOTAL ))
echo "HIG Compliance Score: $SCORE%"
echo ""
if [ $SCORE -ge 90 ]; then
echo -e "${GREEN}Excellent! Your design follows Apple HIG.${NC}"
elif [ $SCORE -ge 70 ]; then
echo -e "${YELLOW}Good, but needs improvements.${NC}"
else
echo -e "${RED}Needs significant improvements to match Apple HIG.${NC}"
fi
fi
echo ""
print_info "Apple HIG Recommendations:"
echo " 1. Use system fonts with Dynamic Type"
echo " 2. Use semantic colors for dark mode"
echo " 3. Add VoiceOver labels to all images"
echo " 4. Ensure 44x44pt minimum touch targets"
echo " 5. Follow 8pt grid system"
echo " 6. Use large titles for top-level navigation"
echo " 7. Limit TabView to 3-5 tabs"
echo " 8. Use .borderedProminent for primary actions only"
echo ""
print_info "Resources:"
echo " - Apple HIG: https://developer.apple.com/design/human-interface-guidelines/"
echo " - SF Symbols: https://developer.apple.com/sf-symbols/"
echo " - WWDC Videos: https://developer.apple.com/videos/"
echo ""
# Exit code based on failures
if [ $FAIL_COUNT -gt 0 ]; then
exit 1
else
exit 0
fi