Last active
May 3, 2025 12:33
-
-
Save allisonpaigemcentire/719b856796d599e9d758e8a1343b5bd8 to your computer and use it in GitHub Desktop.
SwiftUI Accessibility Refactor Script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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