Initial commit
This commit is contained in:
393
skills/apple-hig-designer/scripts/validate_design.sh
Executable file
393
skills/apple-hig-designer/scripts/validate_design.sh
Executable 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
|
||||
Reference in New Issue
Block a user