SKStoreReviewController,如何正确使用?

46

我看过一些答案,但对它们不满意并得到了一些想法,但不知道如何正确使用它,使它以正确的方式执行,虽然我认为它应该在App代理中使用didFinishLaunching,但在没有任何麻烦的情况下实现它之前,我想确认一下。

SKStore​Review​Controller 只适用于iOS 10.3,据我所读,有人能用一点Swift和Objective C的代码进行解释吗?

更新:

实际上,我对调用request​Review()方法感到困惑,在哪里需要调用此方法?在rootViewControllerviewDidLoad还是在appDelegatedidFinishlaunching中?

谢谢。


不明白为什么会被踩.. - Abhishek Mitra
这可能会有所帮助,看看如何设置策略:https://www.behradbagheri.com/boringb-tutorials/2017/4/a-proper-way-to-request-review-using-skstorereviewcontroller-in-ios-103-and-higher - Maziyar
6个回答

78

SKStoreReviewController可用于iOS 10.3及更高版本。

根据苹果文档:

您可以在用户使用应用程序时要求他们对其进行评分或评论,而无需将他们发送到App Store。您确定调用API的用户体验要点,系统会负责其余操作。

为了在应用程序内显示评分/评论,您必须添加StoreKit框架。

请查找以下两种语言的样本代码:

Objective C:

#import <StoreKit/StoreKit.h>

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

从xCode 9开始,您可以这样做:

#import <StoreKit/StoreKit.h>

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

迅捷:

import StoreKit

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

更新: 仅在用户展现出对您的应用程序的参与之后再请求评分


1
这取决于您的需求,您想在哪里显示它。 - korat prashant
2
您可以在用户体验的适当时机要求客户在应用商店上对您的应用进行评分和评论。 - korat prashant
1
在 Objective-C 中,您应该使用 @import StoreKit。使用 Apple 框架时,阅读和理解起来会更加容易。 - Ethan Allen
3
自 Xcode 9 开始,您可以使用 if(@available(iOS 10.3, *)) 替代 if ([SKStoreReviewController class]) - vmeyer
3
如果你的应用处于测试阶段,如在模拟器或测试设备中,则发送按钮将自动禁用。一旦您的应用程序上线,发送按钮将自动显示。 - Faiz Fareed
显示剩余9条评论

9

对于Swift语言,

import StoreKit

当您想要请求时,请添加以下代码。

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

对于Objective C,

1-) 从Link Binary With Library中添加了StoreKit框架。 enter image description here

2-) 添加了框架。

#import <StoreKit/StoreKit.h>

3-) 在需要调用App-Review弹窗的位置添加以下代码。在这个例子中,我将其添加到了viewDidLoad方法中。

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

4-) 以下是苹果公司的说明,当你在调试模式下进行测试时,请注意:

当你在应用程序还处于开发模式时调用此方法,将始终显示一个评分/评论请求视图,以便您测试用户界面和体验。但是,当您在使用TestFlight分发的应用程序中调用它时,此方法没有任何效果。


在生产中,“无效”是什么意思? - user7219266
您的评分和评论不会显示在应用商店中。 - Emre Gürses
1
为什么“发送”按钮会变灰?此外,这要求用户对应用进行评分(从1到5颗星),但我不知道如何要求用户撰写评论? - user7219266
2
点击评分星星后,@平衡评论按钮将会显示。因此,在开发模式下,您永远不会测试此功能。 - Emre Gürses

7
我认为直接调用以下内容并不是一个好主意:

SKStoreReviewController.requestReview()

你可以在用户打开你的应用程序时,每10次(10,20,30,...100)显示评论请求。因此,首先需要创建一个文件,负责保存您的应用程序在用户默认设置中的打开计数,检索应用程序打开计数,并显示requestReview()。请查看以下代码片段,这个文件将会负责一切。
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.
        }
    }
    
}

4

在korat上面的出色答案中,我想再补充一点......

如果你正在支持一个传统的Objective-C应用程序,并且想在几个应用程序打开后调用DisplayReviewController,则需要执行以下操作:

在AppDelegate.m类中添加以下内容:

- (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"];
}

在控制器中,你想要调用这个函数:
- (void)applicationDidBecomeActive {

  if ([[NSUserDefaults standardUserDefaults] integerForKey:@"LaunchCount"] == 5) {
     [self DisplayReviewController];
  }
}

2

我认为你可以实现一种方法来计算用户运行应用的次数,并将其存储在UserDefaults中,如果计数数字是5或10或类似的数字(这取决于你),则调用requestReview(),这样你就有更多获得好评的机会。


1
这是我正在开发的一项实用函数,适用于我的使用情况,可能会帮助很多其他人。(请随意批评和改进/纠正我的代码:D)。我正在开发一个语音练习应用程序,我想在用户录制几次后请求评级。我将添加主要功能,然后是其他辅助函数。简要逻辑是,您可以每年请求3次评论,因此如果1年已过去,我将重置询问计数为0。此外,并非每次都会出现评论请求。因此,在不允许应用程序尝试评论请求之前,我设有上限为30个请求。如果应用程序版本更改,则不会考虑此限制,因为您可以再次要求新应用程序版本的评论。
/// 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)
    }
}

字典键:

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

辅助函数:
/// 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://dev59.com/ll4d5IYBdhLWcg3wFPQ7
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://dev59.com/214c5IYBdhLWcg3wCGVJ
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
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接