SKStore​Review​Controller, How to use it in a correct way?
Asked Answered
G

6

46

I have seen some answer but not satisfied with them and got some idea, but don't know how to use it properly, so that it will execute in proper way, though i think it should be used in App delegates didFinishLaunching, but i wanted to be sure before implement it in Live app without any hustle. SKStore​Review​Controller is only work for ios 10.3 what i read, could anybody explain with little bit of code in swift and objective c.

UPDATE:

Actually I'm confused about calling the method request​Review(), Where do i need to call this method? in rootViewController's viewDidLoad or in appDelegate's didFinishlaunching ?

Thanks.

Giese answered 3/4, 2017 at 5:42 Comment(2)
Don't understand why got down vote..Giese
This may help to see how to set up a strategy: behradbagheri.com/boringb-tutorials/2017/4/…Cannibalism
U
78

SKStoreReviewController is available in iOS 10.3 and later.

According to APPLE's Documents:

You can ask users to rate or review your app while they're using it, without sending them to the App Store.You determine the points in the user experience at which it makes sense to call the API and the system takes care of the rest.

Inorder to display Rate/Review inside the app, you have to add StoreKitframework.

Please find the Sample code for both language:

Objective C:

#import <StoreKit/StoreKit.h>

- (void)DisplayReviewController {
    if([SKStoreReviewController class]){
       [SKStoreReviewController requestReview] ;
    }
}

since xCode 9 you can do:

#import <StoreKit/StoreKit.h>

- (void)DisplayReviewController {
    if (@available(iOS 10.3, *)) {
        [SKStoreReviewController requestReview];
    }
}

Swift:

import StoreKit

func DisplayReviewController {
    if #available( iOS 10.3,*){
        SKStoreReviewController.requestReview()
    }
}

Update: Ask for a rating only after the user has demonstrated engagement with your app

Unnecessary answered 5/4, 2017 at 9:51 Comment(14)
Ok, where i need to put this code ?? in appDelegate ?Giese
It depends on your requirement where you want to display it.Unnecessary
You have added the code inside a UIButton Action, thats mean, user have to click on that button to get review dialogue box ! I think it should be handled automatically by ios as what i read, I'm little confused about where to put the code in appDelegates didFinishLaunching or in rootViewcontroller's viewDidLoad .Giese
Also According to documentation developer.apple.com/reference/storekit/skstorereviewcontroller/… What apple declare is "this method may or may not present an alert, it's not appropriate to call it in response to a button tap or other user action."Giese
Updated - It is only for demonstration purpose, otherwise [SKStoreReviewController requestReview] will auto display system alert.Unnecessary
okay, now where should i call this method? and what will be the best practice to put this code ` SKStoreReviewController.requestReview() ` in didfinishLaunching or rootViewController's viewDidLoad ?Giese
You can ask your customers to rate and review your app on the App Store at appropriate times throughout the user experience.Unnecessary
You should use @import StoreKit with Objective-C. So much easier to read and understand when using Apple Frameworks.Echovirus
Since Xcode 9, you can use if(@available(iOS 10.3, *)) instead of if ([SKStoreReviewController class])Graycegrayheaded
i added succesfully this component but when the pup-op arrive, user can not click "Send" button because of disabled automaticly. Anyone know what's the reason for this scenario?Weintrob
@EmreGürses, if your app is in testing phases i.e in simulator or test device, send button will be disabled automatically, Once your app is live, send button will be visible automatically.Aniseed
@FaizFareed That's right, we have to test in live version :)Weintrob
@EmreGürses i'm searching for the same thing, you guys saved my time. ThanksStockstill
I would also keep in mind that "The system automatically limits the display of the prompt to three occurrences per app within a 365-day period" according to Apple docs. So you might not see it after the first three successful tests.Cortneycorty
I
9

For Swift,

import StoreKit

Add below code to request when you want to ask.

if #available(iOS 10.3, *) {
        SKStoreReviewController.requestReview()
    }

For Objective C,

1-) Added StoreKit framework from Link Binary With Library enter image description here

2-) Added framework

#import <StoreKit/StoreKit.h>

3-) Added below code where you want to call App-Review pop-up. In this case, i added in viewDidLoad.

  - (void)viewDidLoad {
        [super viewDidLoad];
        [SKStoreReviewController requestReview];
    }

4-) You should be aware of below explain from Apple, When you test in debug mode

When you call this method while your app is still in development mode, a rating/review request view is always displayed so that you can test the user interface and experience. However, this method has no effect when you call it in an app that you distribute using TestFlight.

Irisation answered 22/9, 2017 at 6:17 Comment(4)
What do you mean by "no effect" in production?Sulphone
Your rate and comments will not shown on App Store.Weintrob
This is why the "send" button is gray out? Moreover this asks the user to rate the App (from 1 to 5 stars) but I don't know how to ask the user to also write a review ?Sulphone
@Balanced review buttons will show you after clicking the rate stars. So when you are in development mode, you will never test this feature.Weintrob
J
7

I think directly calling the below is not an good idea

SKStoreReviewController.requestReview()

It can be done like whenever user opens your app the multiple of 10(10,20,30,...100) then you can show for review

so first of all you need to create a file that will be responsible for everything like saving your application open count in user defaults , retrieving application open count, and showing requestReview() kindly have a look at the following code snippet

import Foundation
import StoreKit
class  SpsRateManager {
    
    
    private static let instance = SpsRateManager()
    
    var shareinstance: SpsRateManager{
        return .instance
    }
    static func incrementAppOpenedCount() { // called from appdelegate didfinishLaunchingWithOptions:
        let userdefault = UserDefaults.standard
        
        
        let savedvalue = userdefault.integer(forKey: Configuration.APPLICATIONOPENCOUNTSTATUS)
        if savedvalue == 0 {
            print("Not saved ")
            userdefault.set(1, forKey: Configuration.APPLICATIONOPENCOUNTSTATUS)
        }
        else{
            userdefault.set(savedvalue+1, forKey: Configuration.APPLICATIONOPENCOUNTSTATUS)
            
        }
        
    }
    
    static func checkAppopencountandProvideReview(){
        let userdefault = UserDefaults.standard
        
        
        let appopencountvalue  = userdefault.integer(forKey: Configuration.APPLICATIONOPENCOUNTSTATUS)
        if appopencountvalue % 10 == 0 {
            print("its been 10 times so ask for review ")
            SpsRateManager().requestReview()
        }
        else{
            print("not enough open count dont show ")
        }
        
    }
    
    
    
    
    fileprivate func requestReview() {
        if #available(iOS 10.3, *) {
            SKStoreReviewController.requestReview()
        } else {
            // Fallback on earlier versions
            // Try any other 3rd party or manual method here.
        }
    }
    
}
Jemma answered 3/4, 2018 at 12:30 Comment(0)
T
4

Adding onto korat's great answer above...

If your supporting a legacy Objective-C app and you want to call DisplayReviewController after a few app opens then do the following:

In your class AppDelegate.m add this:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  int count = [[NSUserDefaults standardUserDefaults] integerForKey:@"LaunchCount"];
  if(count < 0) count = 0;
  [[NSUserDefaults standardUserDefaults] setInteger:count+1 forKey:@"LaunchCount"];
}

//The application was in background and become active
- (void)applicationWillEnterForeground:(UIApplication *)application {
  int count = [[NSUserDefaults standardUserDefaults] integerForKey:@"LaunchCount"];
  if(count < 0) count = 0;
  [[NSUserDefaults standardUserDefaults] setInteger:count+1 forKey:@"LaunchCount"];
}

and in the controller you want to call the function:

- (void)applicationDidBecomeActive {

  if ([[NSUserDefaults standardUserDefaults] integerForKey:@"LaunchCount"] == 5) {
     [self DisplayReviewController];
  }
}
Theotokos answered 26/7, 2018 at 16:37 Comment(0)
S
2

I think you may implement a method to count when they run the app and store it in UserDefaults, then call requestReview() if the count number is 5 or 10 or something like that (it depends on you), by this way you have more chance of getting a good review.

Selassie answered 6/9, 2017 at 10:20 Comment(0)
H
1

Here's a utility function I am developing for my own use case that might help a lot of other people. (Feel free to roast and improve/correct my code :D). I am working on a speech practice app and I want to ask for a rating after the user has done a few recordings. I will add the main function and then other helper functions used below it. The brief logic is, you can request a review 3 times a year, so if 1 year has passed, I reset the ask count to 0. Also, the review request won't be presented for each ask. So I have an upper limit of 30 asks before I don't allow the app to attempt review requests anymore. This won't be taken into consideration if the app version has changed, as you can again ask for a review for the new app version.

/// Requests review from user based on certain conditions.
/// 1. Should have recorderd at least 3 recordings (if you want to force attept a review ask don't pass any parameter)
/// 2. Has not already asked for a review today
/// 3. A probabitly of 50% if will ask today
/// 4. If review has not been asked more than 30 times in the same year for the current version
/// - Parameter numberOfRecordings: If the number of recordings is greater than 3 then a review will be asked.
func askForReview(numberOfRecordings: Int = 5) {
    let defaults = UserDefaults.standard
    let lastAskedReviewAt = defaults.double(forKey: lastAskedReviewAtKey)
    let dateStringForLastReviewAsk = getDateString(from: lastAskedReviewAt)
    let dateForLastReviewAsk = getDate(from: dateStringForLastReviewAsk) ?? Date(timeIntervalSince1970: 0)
    let askedReviewToday = Calendar.current.isDateInToday(dateForLastReviewAsk)
    var appReviewRequestsCount = defaults.integer(forKey: appReviewRequestsCountKey)
    
    if Date().localDate().years(from: dateForLastReviewAsk) >= 1 {
        defaults.setValue(0, forKey: appReviewRequestsCountKey)
        appReviewRequestsCount = 0
    }
    
    var isAskingReviewForSameVersion = false
    
    if let currentlyInstalledVersion = getInstalledVersionNumber(), let lastReviewAskedForVersion = defaults.string(forKey: lastReviewAskedForVersionKey) {
        if currentlyInstalledVersion == lastReviewAskedForVersion {
            isAskingReviewForSameVersion = true
        } else {
            appReviewRequestsCount = 0
            defaults.setValue(0, forKey: appReviewRequestsCountKey)
        }
    }
    
    let askingReviewTooManyTimes = appReviewRequestsCount >= 30 && isAskingReviewForSameVersion
    
    let totalRecordingsTillDateCount = defaults.integer(forKey: totalRecordingsTillDateCountKey)
    let localNumberOfRecordings = max(numberOfRecordings, totalRecordingsTillDateCount)
    
    if localNumberOfRecordings > 3 && Bool.random() && !askedReviewToday && !askingReviewTooManyTimes {
        SKStoreReviewController.requestReview()
        defaults.setValue(Date().timeIntervalSince1970, forKey: lastAskedReviewAtKey)
        if let versionNumber = getInstalledVersionNumber() {
            defaults.setValue(versionNumber, forKey: lastReviewAskedForVersionKey)
        }
        defaults.setValue(appReviewRequestsCount + 1, forKey: appReviewRequestsCountKey)
    }
}

Dictionary Keys:

let lastAskedReviewAtKey = "LastAskedReviewAt"
let appReviewRequestsCountKey = "AppReviewRequestsCount"
let lastReviewAskedForVersionKey = "AskedReviewForVersion"
let appVersionNumberKey = "CFBundleShortVersionString"

Helper Functions:

/// Get a string representation in current local time for a timestamp
/// - Parameter timestamp: Timestamp to be converted to date string
/// - Returns: A date string from passed timestamp in dd MMM yyy format
func getDateString(from timestamp: Double) -> String {
    let dateFormatter = getDateFormatter()
    let date = Date(timeIntervalSince1970: timestamp)
    let dateString = dateFormatter.string(from: date)
    return dateString
}

/// Get a date from a string of date format dd MMM yyyy.
/// - Parameter dateString: Date string formated as dd MMM yyyy
/// - Returns: A date object by parsing date in dd MMM yyy format
func getDate(from dateString: String) -> Date? {
    //        print("Date String: ", dateString)
    let dateFormatter = getDateFormatter()
    return dateFormatter.date(from: dateString) ?? nil
}


//Ref: https://mcmap.net/q/109038/-getting-the-difference-between-two-dates-months-days-hours-minutes-seconds-in-swift
extension Date {
    /// Returns the amount of years from another date
    func years(from date: Date) -> Int {
        return Calendar.current.dateComponents([.year], from: date, to: self).year ?? 0
    }
    /// Returns the amount of months from another date
    func months(from date: Date) -> Int {
        return Calendar.current.dateComponents([.month], from: date, to: self).month ?? 0
    }
    /// Returns the amount of weeks from another date
    func weeks(from date: Date) -> Int {
        return Calendar.current.dateComponents([.weekOfMonth], from: date, to: self).weekOfMonth ?? 0
    }
    /// Returns the amount of days from another date
    func days(from date: Date) -> Int {
        return Calendar.current.dateComponents([.day], from: date, to: self).day ?? 0
    }
    /// Returns the amount of hours from another date
    func hours(from date: Date) -> Int {
        return Calendar.current.dateComponents([.hour], from: date, to: self).hour ?? 0
    }
    /// Returns the amount of minutes from another date
    func minutes(from date: Date) -> Int {
        return Calendar.current.dateComponents([.minute], from: date, to: self).minute ?? 0
    }
    /// Returns the amount of seconds from another date
    func seconds(from date: Date) -> Int {
        return Calendar.current.dateComponents([.second], from: date, to: self).second ?? 0
    }
    /// Returns the a custom time interval description from another date
    func offset(from date: Date) -> String {
        if years(from: date)   > 0 { return "\(years(from: date))y"   }
        if months(from: date)  > 0 { return "\(months(from: date))M"  }
        if weeks(from: date)   > 0 { return "\(weeks(from: date))w"   }
        if days(from: date)    > 0 { return "\(days(from: date))d"    }
        if hours(from: date)   > 0 { return "\(hours(from: date))h"   }
        if minutes(from: date) > 0 { return "\(minutes(from: date))m" }
        if seconds(from: date) > 0 { return "\(seconds(from: date))s" }
        return ""
    }
}

//Ref: https://mcmap.net/q/369458/-swift-get-local-date-and-time
extension Date {
    func localDate() -> Date {
        let nowUTC = Date()
        let timeZoneOffset = Double(TimeZone.current.secondsFromGMT(for: nowUTC))
        guard let localDate = Calendar.current.date(byAdding: .second, value: Int(timeZoneOffset), to: nowUTC) else {return Date()}

        return localDate
    }
}

func getInstalledVersionNumber() -> String? {
    guard let infoDictionary = Bundle.main.infoDictionary, let currentVersionNumber = infoDictionary[appVersionNumberKey] as? String else { return nil}
    return currentVersionNumber
}
Headset answered 5/2, 2021 at 14:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.