App Store ratings affect downloads more directly than most developers account for. When your app sits next to a competitor in search results, the difference between 4.5 stars and 3.8 stars changes click-through rates significantly. The quickest way to improve ratings in a Rork app is to ask at exactly the right moment — not too early, not too late, and never during a frustrating moment.
Apple's SKStoreReviewManager lets you show a native review dialog directly inside your app. But it comes with a hard cap: three times per year per device. Waste those three opportunities on bad timing and you've lost your best shots for that user.
Key Constraints to Know Before Implementing
- 3 times per year: The dialog shows at most 3 times on the same device for the same app per calendar year. Additional calls are silently ignored.
- Apple decides: Even when you call
requestReview(), Apple's system may choose not to display the dialog based on its own criteria. - Doesn't work in TestFlight: The dialog is suppressed during beta testing. You can only verify the timing logic in production.
- No manipulation allowed: You can't influence what rating a user gives or pre-filter users. Apple's guidelines prohibit "ratings gates."
Implementing In-App Review in a Rork App
Give Rork a prompt like this to generate the core implementation:
Add App Store review request to the app.
- Show after the user completes 3 sessions
- Don't show again for 30 days after each request
- Track state with UserDefaults
Here's what a solid implementation looks like:
import SwiftUI
import StoreKit
class ReviewManager: ObservableObject {
private let sessionCountKey = "review_session_count"
private let lastReviewDateKey = "review_last_requested"
private let minimumSessionCount = 3
private let minimumDaysBetweenRequests = 30
var shouldRequestReview: Bool {
let sessionCount = UserDefaults.standard.integer(forKey: sessionCountKey)
guard sessionCount >= minimumSessionCount else { return false }
if let lastDate = UserDefaults.standard.object(forKey: lastReviewDateKey) as? Date {
let days = Calendar.current.dateComponents(
[.day], from: lastDate, to: Date()
).day ?? 0
guard days >= minimumDaysBetweenRequests else { return false }
}
return true
}
func incrementSessionCount() {
let current = UserDefaults.standard.integer(forKey: sessionCountKey)
UserDefaults.standard.set(current + 1, forKey: sessionCountKey)
}
func requestReviewIfAppropriate(in scene: UIWindowScene?) {
guard shouldRequestReview, let scene = scene else { return }
SKStoreReviewManager.requestReview(in: scene)
UserDefaults.standard.set(Date(), forKey: lastReviewDateKey)
UserDefaults.standard.set(0, forKey: sessionCountKey)
}
}
struct ContentView: View {
@StateObject private var reviewManager = ReviewManager()
@Environment(\.scenePhase) private var scenePhase
var body: some View {
NavigationStack {
MainContentView()
}
.onChange(of: scenePhase) { _, newPhase in
if newPhase == .active {
reviewManager.incrementSessionCount()
// Small delay so the UI is fully visible before the dialog
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
let scene = UIApplication.shared.connectedScenes
.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene
reviewManager.requestReviewIfAppropriate(in: scene)
}
}
}
}
}Timing Strategy: When to Actually Ask
The three annual opportunities are valuable. Use them when satisfaction is highest.
Moments that work well:
- Immediately after completing a meaningful task (cleared a game level, hit a goal, finished something)
- After 3–5 sessions — enough that the user has formed an opinion
- After the user actively engages with a key feature
Moments to avoid:
- Right after opening the app for the first time
- After an error or loading failure
- In the middle of a multi-step flow
App-type-specific examples: Game apps see good conversion on level completion. Habit apps work well on streak milestones (7-day streak). Productivity apps convert well after reaching a task completion count.
// Trigger on meaningful achievement
struct TaskCompletionView: View {
@StateObject private var reviewManager = ReviewManager()
@State private var completedTasks = 0
var body: some View {
VStack {
Text("Tasks completed: \(completedTasks)")
Button("Complete task") {
completedTasks += 1
// Ask after 5 completions
if completedTasks == 5 {
let scene = UIApplication.shared.connectedScenes
.first as? UIWindowScene
reviewManager.requestReviewIfAppropriate(in: scene)
}
}
}
}
}Complementary Approaches
Feedback button in settings: Before a frustrated user writes a negative App Store review, give them an easier path. A "Send Feedback" button routed to email or a support form catches negative sentiment early.
Button("Send Feedback") {
let subject = "App Feedback"
let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown"
let body = "App version: \(version)\n\n"
let urlString = "mailto:support@example.com?subject=\(subject.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")&body=\(body.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")"
if let url = URL(string: urlString) {
UIApplication.shared.open(url)
}
}Direct App Store review link: For users who express satisfaction (through a thumbs up button, for instance), link directly to the write-review page:
func openAppStoreReview(appID: String) {
let urlString = "https://apps.apple.com/app/id\(appID)?action=write-review"
if let url = URL(string: urlString) {
UIApplication.shared.open(url)
}
}The most important thing is that the underlying app is worth rating highly. No amount of timing optimization fixes an app that frustrates users. Build something useful with Rork, ask for a review at the moment users feel that value — that combination compounds over time.