Skip to content

Instantly share code, notes, and snippets.

@allisonpaigemcentire
Last active May 3, 2025 12:33
Show Gist options
  • Save allisonpaigemcentire/719b856796d599e9d758e8a1343b5bd8 to your computer and use it in GitHub Desktop.
Save allisonpaigemcentire/719b856796d599e9d758e8a1343b5bd8 to your computer and use it in GitHub Desktop.
SwiftUI Accessibility Refactor Script
# make_accessible.py
import os
import sys
import re
from openai import OpenAI
INJECTION_PATTERNS = [
r"ignore.*instructions", r"disregard.*above", r"assistant.*role", r"user.*role",
r"only respond with", r"do not follow", r"system:.*", r"@role", r"::"
]
def is_potentially_injected(content: str) -> bool:
return any(re.search(pattern, content, re.IGNORECASE) for pattern in INJECTION_PATTERNS)
def extract_class_name(file_content):
match = re.search(r'\b(struct|class|enum)\s+(\w+)', file_content)
return match.group(2) if match else "UnknownClass"
def generate_accessibility_prompt(file_name, class_name, file_content):
return f"""
You are a senior SwiftUI engineer and expert in iOS accessibility. You specialize in refactoring SwiftUI views to conform to Apple's Human Interface Guidelines (HIG) and WCAG 2.1, with full support for VoiceOver, Voice Control, keyboard navigation, Dynamic Type, Assistive Access, and UI testing best practices.
## Task Overview:
Before making any modifications to the provided SwiftUI view, **analyze the file** using a technique similar to `app.performAccessibilityAudit()` in UI tests.
First, **identify and list all accessibility violations** that would likely occur, organized by category:
- **Control Violations**:
- Missing `.accessibilityLabel()`, `.accessibilityHint()`, or `.accessibilityIdentifier()`
- Interactive elements not focusable via keyboard (`.focusable(true)` missing)
- Inappropriate or missing `.accessibilityAddTraits()` (e.g., `.isButton`)
- **Text Violations**:
- Text that does not scale with Dynamic Type
- Text clipping, truncation, or missing `.minimumScaleFactor()`
- **Navigation & Structure Violations**:
- Visual order not matching VoiceOver reading order
- Grouped views missing `.accessibilityElement(children: .combine)`
- Missing `.accessibilitySortPriority(...)` for reading order management
- **Assistive Access Violations**:
- Small tap targets (<44x44 points)
- Use of non-standard gestures or non-standard SwiftUI controls
- Layout breakage at large Dynamic Type sizes (e.g., `.extraExtraExtraLarge`)
---
## Prompting Techniques to Use:
- **Think step-by-step** (Chain of Thought) to find issues before fixing.
- **Reflect and verify** your analysis against WCAG and HIG standards (Self-Consistency).
---
## Accessibility Refactor Objectives:
After identifying violations:
1. Regenerate the SwiftUI view with **full accessibility support applied**.
2. Ensure:
- All UI elements have meaningful `.accessibilityLabel()` and `.accessibilityHint()`.
- For example: Use hints like "Tap \\(answerChoice)" so the user can say "Tap Deep Dish Pizza."
- Visible label text appears at the beginning of custom accessibility labels if changed.
- Full support for Dynamic Type using `.font(.preferredFont(forTextStyle:))`, `.system(...) relativeTo:` or `.custom(..., relativeTo:)`.
- Keyboard navigation is fully supported using `.focusable(true)`.
- Layout does not break under large accessibility text settings.
---
## Explainability Requirements:
- For **each issue found**, cite the relevant WCAG 2.1 success criterion or HIG recommendation.
- Explanations must be in **plain English**, suitable for junior developers and designers.
---
## Ethical Requirements:
- Accessible labels and hints must be **inclusive, unbiased, respectful**, and use **universal phrasing**.
---
## Output Requirements:
1. **First**: List all accessibility violations found in the original SwiftUI view.
2. **Then**: Return the fully updated SwiftUI code, applying changes with **in-line comments** explaining each improvement.
3. **Output the updated view ONLY** β€” do not summarize or add additional commentary after the code.
---
## Provided File to Audit and Refactor:
πŸ”½ SwiftUI view to refactor:
START_OF_FILE
{file_content}
END_OF_FILE
"""
def generate_accessibility_for_file(swift_file_path, client):
try:
with open(swift_file_path, 'r') as file:
content = file.read()
if not content.strip():
print(f"❌ File '{swift_file_path}' is empty or unreadable.")
return
# πŸ” DEBUG: Preview the file content
print(f"πŸ“‚ Reading: {swift_file_path}")
print("πŸ§ͺ File content preview (first 10 lines):")
print("─────────────────────────────")
for line in content.splitlines()[:10]:
print(line)
print("─────────────────────────────")
class_name = extract_class_name(content)
prompt = generate_accessibility_prompt(swift_file_path, class_name, content)
print(f"πŸš€ Sending {class_name} to GPT-4...")
response = client.chat.completions.create(
model="gpt-4-0125-preview",
messages=[
{"role": "system", "content": "You must follow the user's output format exactly: return Swift code only with no commentary or Markdown."},
{"role": "user", "content": prompt}
],
temperature=0.2,
max_tokens=400
)
test_code = response.choices[0].message.content
# πŸ”’ Check for potential injection before writing
if is_potentially_injected(test_code):
print(f"⚠️ Potential injection detected in response for {swift_file_path}.")
print("πŸ›‘ Aborting test file generation for security reasons.")
return
output_file = f"{class_name}Accessible.swift"
with open(output_file, 'w') as f:
f.write(test_code)
print(f"βœ… Test file written: {output_file}")
except Exception as e:
print(f"❌ Error generating tests for {swift_file_path}: {e}")
def main():
if len(sys.argv) != 2:
print("Usage: python3 generate_accessibility_for_file.py <YourFile.swift>")
return
file_path = sys.argv[1]
if not os.path.isfile(file_path):
print(f"❌ File not found: {file_path}")
return
api_key = os.getenv("OPENAI_API_KEY")
if not api_key:
print("πŸ” Set your OpenAI API key with: export OPENAI_API_KEY=sk-...")
return
client = OpenAI(api_key=api_key)
generate_accessibility_for_file(file_path, client)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment