Wei Wen
13 July 2018
@available(iOS 10.3, *)
open class SKStoreReviewController : NSObject {
/** Request StoreKit to ask the user for an app review.
* This may or may not show any UI.
*
* Given this may not succussfully present an alert to
* the user, it is not appropriate for use from a button
* or any other user action. For presenting a write
* review form, a deep link is available to the App
* Store by appending the query params
* "action=write-review" to a product URL.
*/
open class func requestReview()
}
Prompt users to rate our app when they make a "satisfactory purchase":
Trivial
// BPOrderViewController
typedef enum : NSUInteger {
BPOrderEntryContextFromDirectPayment,
BPOrderEntryContextFromHistoryList,
BPOrderEntryContextFromNotificationMessage,
BPOrderEntryContextFromMerchantPayment,
} BPOrderEntryContext;
Trivial
// BPOrderObject
- (BOOL)isFinalized
{
return self.status == BPOrderObjectStatusCompleted || self.status == BPOrderObjectStatusCompleting;
}
Same as criteria 1 (straightaway successful purchase)
BPOrderObject.isWelcomeGift()
returns whether it is a free giftgiftID
corresponding to the order as the user goes through the purchase flowtopupPayableAmount < paymentPayableAmount
paymentCashAmount != 0
Trivial?
Trivial
Actually guaranteed by Apple
Sloppy code style
Misuse of language features
guard
semanticsguard
isDirectPurchase && order.isFinalized(),
giftIDs[order.orderID].map({ $0 != 0 }) ?? false
|| order.isWelcomeGift()
|| order.isDiscounted()
|| order.hasRebate(),
isLoyalCustomer(dateFirstOpenedApp: dateFirstOpenedApp),
!hasSeenPromptRecently(datelastReviewed: dateLastReviewed)
else {
return false
}
if !order.isFinalized() {
return false
}
if !(isWelcomeGift(order: order)
|| order.isFreeGift()
|| order.isDiscounted()
|| order.hasRebate()) {
return false
}
if !isLoyalCustomer() {
return false
}
if hasSeenPromptRecently() {
return false
}
TutorialKit
checks whether user has seen tutorial already and showsTutorialView
rendering of steps and handles animationsTutorialStep
stores data on each step (what to target, what to render)TutorialTarget
represents a target to highlightlet tutorial = TutorialKit(
name: "tutorial-demo-example",
makeSteps: { [weak self] in
guard let this = self else {
return []
}
return [
TutorialStepText(target: this.label, text: "This is a text view"),
TutorialStepText(
target: CGRect(x: 100, y: 100, width: 100, height: 100),
text: "This is an arbitrary frame"
),
TutorialStepText(
target: TutorialTargetTableViewCell(this.table, at: [0, 10]) {
return $0.detailTextLabel
},
text: "This is a table view cell's detail")
)
]
}
)
weakify(self);
_tutorial = [[TutorialKit alloc] initWithName:@"tutorial-kit-demo" makeSteps:^NSArray * {
strongify(self);
NSMutableArray *steps = [[NSMutableArray alloc] init];
[steps addObject:[[TutorialStepText alloc]
initWithTarget:self.label
text:@"This is a text view"]];
[steps addObject:[[TutorialStepText alloc]
initWithTarget: [[TutorialTargetCGRect alloc]
init:CGRectMake(100, 100, 100, 100)]
text:@"This is an arbitrary frame"]];
[steps addObject:[[TutorialStepText alloc]
initWithTarget: [[TutorialTargetTableViewCell alloc]
init:self.table
at: [NSIndexPath indexPathForRow:10 inSection:0]
targetForCell:^UIView * (UITableViewCell *cell) {
return cell.detailTextLabel;
}]
text:@"This is a table view cell's detail"]];
return steps;
}]
public class TutorialKit: NSObject {
public init(name: String, makeSteps: @escaping () -> [TutorialStep]) { ... }
@available(swift, obsoleted: 1.0)
@objc public convenience init(name: String, makeSteps: @escaping () -> [Any]) { ... }
@objc public func start() { ... }
@objc public func forceStart() { ... }
@objc public func hasSeenTutorial() -> Bool { ... }
}
public protocol TutorialStep {
var target: UIView { get }
var willEnter: (() -> Void)? { get set }
var didEnter: (() -> Void)? { get set }
var willExit: (() -> Void)? { get set }
var didExit: (() -> Void)? { get set }
/**
Returns a UIView that should be shown when the step is displayed
- Parameter frame: The frame the UIView should draw within
- Parameter targetFrame: The frame of the view the step is targeting
*/
func makeView(frame: CGRect, targetFrame: CGRect) -> UIView
}
public class TutorialStepPointing: NSObject, TutorialStep { ... }
public class TutorialStepText: TutorialStepPointing { ... }
+ public protocol TutorialTarget { ... }
public protocol TutorialStep {
- var target: UIView { get }
+ var target: TutorialTarget { get }
var willEnter: (() -> Void)? { get set }
var didEnter: (() -> Void)? { get set }
var willExit: (() -> Void)? { get set }
var didExit: (() -> Void)? { get set }
/**
Returns a UIView that should be shown when the step is displayed
- Parameter frame: The frame the UIView should draw within
- Parameter targetFrame: The frame of the view the step is targeting
*/
func makeView(frame: CGRect, targetFrame: CGRect) -> UIView
}
public protocol TutorialTarget {
/**
Will be called before the tutorial step is displayed
- Important: This function should not be animated as it will be wrapped in a UIView.animate()
- Parameter completion: should be called with the target rect after the target is in view
*/
func scrollToFrame(completion: @escaping (CGRect) -> Void)
}
extension UIView: TutorialTarget { ... }
extension CGRect: TutorialTarget { ... }
public class TutorialTargetTableViewCell: NSObject, TutorialTarget { ... }
public class TutorialTargetCollectionViewCell: NSObject, TutorialTarget { ... }
@objc public class TutorialTargetCGRect: NSObject, TutorialTarget { ... }
public class TutorialTargetTableViewCell: NSObject, TutorialTarget {
public init(_ collectionView: UICollectionView,
at indexPath: IndexPath,
targetForCell: @escaping (UICollectionViewCell) -> TutorialTarget? = { $0 })
@available(swift, obsoleted: 1.0)
@objc public convenience init(_ collectionView: UICollectionView,
at indexPath: IndexPath,
targetForCell: @escaping (UICollectionViewCell) -> Any? = { $0 })
...
}
TutorialView
is not customizable