Feedback Form Generator
Generate a production in-app feedback form with category selection, sentiment-based rating, optional screenshot attachment, device diagnostics collection, and smart routing that funnels satisfied users to the App Store review prompt and dissatisfied users to a support channel.
When This Skill Activates
Use this skill when the user:
- Asks to "add a feedback form" or "feedback form"
- Wants "in-app feedback" or "user feedback" collection
- Mentions "bug report form" or "feature request" form
- Asks about "contact support" from within the app
- Wants "feedback collection" with categories or screenshots
- Asks to "route users to App Store review" based on sentiment
Pre-Generation Checks
1. Project Context Detection
- Check Swift version (requires Swift 5.9+)
- Check deployment target (iOS 16+ / macOS 13+)
- Check for @Observable support (iOS 17+ / macOS 14+)
- Identify source file locations
2. Conflict Detection
Search for existing feedback or support code:
Glob: **/*Feedback*.swift, **/*Support*.swift, **/*BugReport*.swift, **/*ContactForm*.swift Grep: "MFMailComposeViewController" or "FeedbackForm" or "SKStoreReviewController"
If third-party feedback SDK found (Instabug, UserVoice, Zendesk):
- Ask if user wants to replace or keep it
- If keeping, don't generate — advise on best practices instead
3. Framework Detection
Check for MessageUI availability:
Grep: "import MessageUI" or "MFMailCompose"
Note: MessageUI is iOS-only. macOS uses NSSharingService or direct webhook delivery.
Configuration Questions
Ask user via AskUserQuestion:
-
Feedback categories? (multi-select)
- Bug Report
- Feature Request
- General Feedback
- Praise
- Other
- All of the above — recommended
-
Delivery method?
- Email (via MFMailComposeViewController / NSSharingService)
- Webhook (POST to a URL endpoint)
- Both — recommended
-
Include screenshot capture?
- Yes — recommended (capture current screen + annotation overlay)
- No
-
Include device diagnostics?
- Yes — recommended (device model, OS, app version, disk, memory)
- No
-
Sentiment routing?
- Yes — recommended (rating >= 4 suggests App Store review, rating <= 2 routes to support)
- No (all feedback goes through the same channel)
Generation Process
Step 1: Read Templates
Read templates.md for production Swift code.
Step 2: Create Core Files
Generate these files:
FeedbackCategory.swift— Enum with SF Symbol icons and display namesFeedbackEntry.swift— Data model for a feedback submissionDeviceDiagnostics.swift— Collects device and app info
Step 3: Create UI Files
FeedbackFormView.swift— SwiftUI form with sentiment, category, message, screenshots
Step 4: Create Delivery Files
FeedbackSubmitter.swift— Protocol + EmailFeedbackSubmitter + WebhookFeedbackSubmitter
Step 5: Create Optional Files
Based on configuration:
ScreenshotCapture.swift— If screenshot capture selected
Step 6: Determine File Location
Check project structure:
- If
Sources/exists ->Sources/Feedback/ - If
App/exists ->App/Feedback/ - Otherwise ->
Feedback/
Output Format
After generation, provide:
Files Created
Feedback/ ├── FeedbackCategory.swift # Category enum with icons ├── FeedbackEntry.swift # Feedback data model ├── DeviceDiagnostics.swift # Device info collector ├── FeedbackFormView.swift # SwiftUI form view ├── FeedbackSubmitter.swift # Email + webhook delivery └── ScreenshotCapture.swift # Screen capture (optional)
Integration Steps
Present the feedback form from any view:
swift@State private var showFeedback = false Button("Send Feedback") { showFeedback = true } .sheet(isPresented: $showFeedback) { FeedbackFormView() }
In a settings screen:
swiftForm { Section("Support") { Button { showFeedback = true } label: { Label("Send Feedback", systemImage: "bubble.left.and.text.bubble.right") } } } .sheet(isPresented: $showFeedback) { FeedbackFormView() }
With a pre-selected category (e.g., from a help menu):
swiftFeedbackFormView(initialCategory: .bugReport)
Testing
swift@Test func feedbackEntryEncodesCorrectly() throws { let entry = FeedbackEntry( category: .bugReport, message: "App crashes when tapping save", rating: 2, screenshots: [], deviceInfo: DeviceDiagnostics.collect(), appVersion: "1.2.3", timestamp: Date() ) let data = try JSONEncoder().encode(entry) let decoded = try JSONDecoder().decode(FeedbackEntry.self, from: data) #expect(decoded.category == .bugReport) #expect(decoded.rating == 2) } @Test func webhookSubmitterSendsCorrectPayload() async throws { let mockSession = MockURLSession() let submitter = WebhookFeedbackSubmitter( url: URL(string: "https://example.com/feedback")!, session: mockSession ) let entry = FeedbackEntry( category: .featureRequest, message: "Dark mode support please", rating: 4, screenshots: [], deviceInfo: DeviceDiagnostics.collect(), appVersion: "1.0.0", timestamp: Date() ) try await submitter.submit(entry) #expect(mockSession.lastRequest?.httpMethod == "POST") #expect(mockSession.lastRequest?.value(forHTTPHeaderField: "Content-Type") == "application/json") } @Test func sentimentRoutingDirectsHighRatingToReview() { let entry = FeedbackEntry( category: .praise, message: "Love this app!", rating: 5, screenshots: [], deviceInfo: DeviceDiagnostics.collect(), appVersion: "1.0.0", timestamp: Date() ) #expect(entry.suggestsAppStoreReview) // rating >= 4 }
Common Patterns
Open feedback form from a menu bar app
swiftButton { showFeedback = true NSApp.activate(ignoringOtherApps: true) } label: { Label("Send Feedback", systemImage: "envelope") } .sheet(isPresented: $showFeedback) { FeedbackFormView() }
Submit feedback with a screenshot attachment
swiftlet screenshot = try await ScreenshotCapture.captureCurrentWindow() let entry = FeedbackEntry( category: .bugReport, message: "Layout is broken on this screen", rating: 1, screenshots: [screenshot], deviceInfo: DeviceDiagnostics.collect(), appVersion: Bundle.main.appVersion, timestamp: Date() ) try await submitter.submit(entry)
Route by sentiment after submission
swiftif entry.suggestsAppStoreReview { // Happy user — ask for App Store review if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene { SKStoreReviewController.requestReview(in: scene) } } else if entry.suggestsSupportFollowUp { // Unhappy user — show support confirmation showSupportConfirmation = true }
Gotchas
- MFMailComposeViewController requires a mail account: Check
MFMailComposeViewController.canSendMail()before presenting. Fall back to webhook ormailto:URL if unavailable. - Screenshot privacy: Consider blurring or redacting sensitive data (passwords, financial info) before attaching. Use
UITextField.isSecureTextEntryareas as a guide. - Attachment size limits for email: Email attachments are typically limited to 10-25 MB. Compress screenshots to JPEG and resize if needed. Webhook delivery is more reliable for large attachments.
- Offline submission queuing: If the device is offline when feedback is submitted, queue the entry to disk and retry when connectivity is restored. Use
NWPathMonitorto detect network changes. - App Store review prompt limits:
SKStoreReviewController.requestReview()is rate-limited by the system (typically 3 times per 365-day period). Don't rely on it always appearing. - macOS has no MFMailComposeViewController: Use
NSSharingService(named: .composeEmail)or prefer webhook delivery on macOS.
References
- templates.md — All production Swift templates
- Related:
generators/review-prompt— App Store review prompt timing and strategy

