6

TL;DR:iOS 上是否有某种方法可以检测 iOS 10.3 中添加的 Storekit App Rating 对话框的存在/显示?

我最近使用以下方法向我的应用程序添加了对新应用程序评级对话框的支持:

[SKStoreReviewController requestReview];

但是,我知道有一些使用注意事项(如此所述),即对话框可能会或可能不会在调用上述函数时出现,包括客户是否已经对应用程序进行评级或客户已经解雇的情况对话 3 次。

我也知道Apple不希望用户操作直接调用对话框的呈现,因此要报告对话框的存在:

尽管您应该在应用程序的用户体验流程中调用此方法,但评分/评论请求视图的实际显示受 App Store 政策的约束。由于此方法可能会或可能不会显示警报,因此在响应按钮点击或其他用户操作时调用它是不合适的。

但这并不能阻止 UX 团队将这些按钮放入图形设计中并询问“我们能否知道对话框是否显示”?

所以,我的问题是,是否有其他间接方式可以确定此对话框的呈现方式?

我最近一直在使用 Appium 对 Android 和 iOS 应用程序进行一些自动化测试,并使用 Xpaths 来查找本机 UI 元素,所以我想知道是否可以在 iOS 应用程序的上下文中实现相同的目标。

4

2 回答 2

14

你的问题让我思考,这比我想象的要容易。

我的第一个想法是检查UIWindow相关的东西 - 快速查看 显示的文档,有UIWindow相关的通知 - 太棒了!我做了一个快速的项目,订阅了所有这些,并展示了审查控制器。这在日志中弹出:

method : windowDidBecomeVisibleNotification:  
object -> <SKStoreReviewPresentationWindow: 0x7fe14bc03670; baseClass = UIApplicationRotationFollowingWindow; frame = (0 0; 414 736); opaque = NO; gestureRecognizers = <NSArray: 0x61800004de30>; layer = <UIWindowLayer: 0x61800003baa0>>

因此,为了检测是否显示了审查控制器,您需要订阅通知并检查它的object属性以找出它的类:

- (void)viewDidLoad {
    [super viewDidLoad];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(windowDidBecomeVisibleNotification:)
                                                 name:UIWindowDidBecomeVisibleNotification
                                               object:nil];
}

- (void)windowDidBecomeVisibleNotification:(NSNotification *)notification {
    if ([notification.object isKindOfClass:NSClassFromString(@"SKStoreReviewPresentationWindow")]) {
        NSLog(@"the review request was shown!");
    }
}

现在请记住,它SKStoreReviewPresentationWindow是不可公开访问的 - 所以你不能简单地编写[SKStoreReviewPresentationWindow class],并且通过使用来欺骗系统NSClassFromString就是这样 - 欺骗系统。不幸的是,另一个最有趣的通知UIWindowDidResignKey没有发出——我希望主窗口会退出,但不幸的是没有。一些进一步的调试也表明主窗口仍然是关键而不是隐藏。您当然可以尝试将 与notification.object进行比较[UIApplication sharedApplication].window,但还显示了其他窗口 -UITextEffectsWindowUIRemoteKeyboardWindow,尤其是在首次显示警报时,它们都不是公开的。

我认为这个解决方案是一种 hack——它很容易被 Apple 改变而破坏它。但最重要的是,这可能是审核期间拒绝的理由,因此使用风险自负。我在 iPhone 7+ 模拟器、iOS 10.3、Xcode 8.3.2 上对此进行了测试


现在,既然我们现在知道检测控制器是否显示是可能的,一个更有趣的问题是如何检测它没有显示。您需要引入一些超时,然后您将执行某些操作,因为未显示警报。这感觉就像您的应用程序被挂起,因此对您的用户来说这将是一个糟糕的体验。另外,我注意到评论控制器没有立即显示,所以苹果不建议在按下按钮后显示它就更有意义了。

于 2017-05-03T00:17:41.583 回答
4

好吧,我已经为这个问题做了一个非常黑客的解决方案:

警告:解决方案包含方法 Swizzling 和对象关联。该解决方案能够通过 Apple 审查,但将来可能会中断。

由于SKStoreReviewPresentationWindow继承UIWindow自我在 UIWindow 上创建了一个类别,因此每当显示或隐藏窗口时都会发布事件:

@interface MonitorObject:NSObject

@property (nonatomic, weak) UIWindow* owner;

-(id)init:(UIWindow*)owner;
-(void)dealloc;

@end

@interface UIWindow (DismissNotification)

+ (void)load;

@end

#import "UIWindow+DismissNotification.h"
#import <objc/runtime.h>

@implementation MonitorObject


-(id)init:(UIWindow*)owner
{
    self = [super init];
    self.owner = owner;
    [[NSNotificationCenter defaultCenter] postNotificationName:UIWindowDidBecomeVisibleNotification object:self];
    return self;

}
-(void)dealloc
{
      [[NSNotificationCenter defaultCenter] postNotificationName:UIWindowDidBecomeHiddenNotification object:self];
}

@end



@implementation UIWindow (DismissNotification)

static NSString* monitorObjectKey = @"monitorKey";
static NSString* partialDescForStoreReviewWindow =  @"SKStore";
+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        SEL originalSelector = @selector(setWindowLevel:);
        SEL swizzledSelector = @selector(setWindowLevel_startMonitor:);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        BOOL didAddMethod =
        class_addMethod(class,
                        originalSelector,
                        method_getImplementation(swizzledMethod),
                        method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(class,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}


#pragma mark - Method Swizzling

- (void)setWindowLevel_startMonitor:(int)level{
    [self setWindowLevel_startMonitor:level];

    if([self.description containsString:partialDescForStoreReviewWindow])
    {
        MonitorObject *monObj = [[MonitorObject alloc] init:self];
        objc_setAssociatedObject(self, &monitorObjectKey, monObj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    }
}

@end

像这样使用它:

订阅事件:

 [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(windowDidBecomeVisibleNotification:)
                                                 name:UIWindowDidBecomeVisibleNotification
                                               object:nil];


 [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(windowDidBecomeHiddenNotification:)
                                                 name:UIWindowDidBecomeHiddenNotification
                                               object:nil];

当事件被触发时对它们做出反应:

- (void)windowDidBecomeVisibleNotification:(NSNotification *)notification
{
    if([notification.object class] == [MonitorObject class])
    {
        NSLog(@"Review Window shown!");
    }
}

- (void)windowDidBecomeHiddenNotification:(NSNotification *)notification
{
    if([notification.object class] == [MonitorObject class])
    {
        NSLog(@"Review Window hidden!");
    }
}

您可以在此处查看解决方案的视频

于 2017-11-23T14:31:15.573 回答