Reputation: 731
I am working on in-app purchases with subscriptions. In swift, you can get price and price locale from the SKProduct like so:
weeklyProduct.price.doubleValue
weeklyProduct.priceLocale.currencySymbol
where weeklyProduct is a SKProduct.
Is it possible to get the free trial length? For example, I specified a two week free trial for the product. can I get this from the SKProduct?
Upvotes: 26
Views: 16241
Reputation: 2365
If you use SwiftyStoreKit, localizedSubscriptionPeriod
is the easiest way
import SwiftyStoreKit
product.introductoryPrice?.localizedSubscriptionPeriod // "1 week"
This is the implementation: https://github.com/bizz84/SwiftyStoreKit/blob/master/Sources/SwiftyStoreKit/SKProductDiscount+LocalizedPrice.swift
Upvotes: 1
Reputation: 1286
OBJECTIVE C
#import "SKProduct+SKProduct.h"
-(NSString*_Nullable)localizedTrialDuraion{
if (@available(iOS 11.2, *)) {
NSDateComponentsFormatter *formatter = [[NSDateComponentsFormatter alloc] init];
[formatter setUnitsStyle:NSDateComponentsFormatterUnitsStyleFull]; //e.g 1 month
formatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorDropAll;
NSDateComponents * dateComponents = [[NSDateComponents alloc]init];
[dateComponents setCalendar:[NSCalendar currentCalendar]];
switch (self.introductoryPrice.subscriptionPeriod.unit) {
case SKProductPeriodUnitDay:{
formatter.allowedUnits = NSCalendarUnitDay;
[dateComponents setDay:self.introductoryPrice.subscriptionPeriod.numberOfUnits];
break;
}
case SKProductPeriodUnitWeek:{
formatter.allowedUnits = NSCalendarUnitWeekOfMonth;
[dateComponents setWeekOfMonth:self.introductoryPrice.subscriptionPeriod.numberOfUnits];
break;
}
case SKProductPeriodUnitMonth:{
formatter.allowedUnits = NSCalendarUnitMonth;
[dateComponents setMonth:self.introductoryPrice.subscriptionPeriod.numberOfUnits];
break;
}
case SKProductPeriodUnitYear:{
formatter.allowedUnits = NSCalendarUnitYear;
[dateComponents setYear:self.introductoryPrice.subscriptionPeriod.numberOfUnits];
break;
}
default:{
return nil;
break;
}
break;
}
[dateComponents setValue:self.introductoryPrice.subscriptionPeriod.numberOfUnits forComponent:formatter.allowedUnits];
return [formatter stringFromDateComponents:dateComponents];
} else {
// Fallback on earlier versions
}
return nil;
}
Upvotes: 0
Reputation: 2235
Here is more compact and short in use version for swift 5, extending SKProductSubscriptionPeriod
Usage:
print("\(period.localizedDescription) free trial")
//Printed example "1 week free trial"
Implementation:
extension SKProductSubscriptionPeriod {
public var localizedDescription: String {
let period:String = {
switch self.unit {
case .day: return "day"
case .week: return "week"
case .month: return "month"
case .year: return "year"
@unknown default:
return "unknown period"
}
}()
let plural = numberOfUnits > 1 ? "s" : ""
return "\(numberOfUnits) \(period)\(plural)"
}
}
Upvotes: -1
Reputation: 829
+ (NSString*)localizedTitleForSKPeriod:(SKProductSubscriptionPeriod*)period{
NSDateComponents *comps = [NSDateComponents new];
NSDateComponentsFormatter *fmt = [NSDateComponentsFormatter new];
switch (period.unit) {
case SKProductPeriodUnitDay:{
fmt.allowedUnits = NSCalendarUnitDay;
comps.day = period.numberOfUnits;
}break;
case SKProductPeriodUnitWeek:{
fmt.allowedUnits = NSCalendarUnitWeekOfMonth;
comps.weekOfMonth = period.numberOfUnits;
}break;
case SKProductPeriodUnitMonth:{
fmt.allowedUnits = NSCalendarUnitMonth;
comps.month = period.numberOfUnits;
}break;
case SKProductPeriodUnitYear: {
fmt.allowedUnits = NSCalendarUnitYear;
comps.year = period.numberOfUnits;
}break;
}
// 1 Day, 1 Week, 2 Weeks, 1 Month, 2 Months, 3 Months, 6 Months, 1 Year
fmt.unitsStyle = NSDateComponentsFormatterUnitsStyleFull;
// One Day, One Week, Two Weeks, etc
//fmt.unitsStyle = NSDateComponentsFormatterUnitsStyleSpellOut;
NSString *s = [[fmt stringFromDateComponents:comps] capitalizedString];
return s;
}
Upvotes: 0
Reputation: 2950
Swift 5
Using Eslam's and Scott's answers as inspiration:
import StoreKit
extension SKProduct {
func priceString() -> String {
let period:String = {
switch self.subscriptionPeriod?.unit {
case .day: return "day"
case .week: return "week"
case .month: return "month"
case .year: return "year"
case .none: return ""
case .some(_): return ""
}
}()
let price = self.localizedPrice!
let numUnits = self.subscriptionPeriod?.numberOfUnits ?? 0
let plural = numUnits > 1 ? "s" : ""
return String(format: "%@ for %d %@%@", arguments: [price, numUnits, period, plural])
}
}
To use:
let price = product.priceString()
print(price)
Result:
THB 89.00 for 7 days
THB 149.00 for 1 month
Upvotes: 0
Reputation: 424
Using Eslam's answer as inspiration I created an extension to SKProduct.PeriodUnit
extension SKProduct.PeriodUnit {
func description(capitalizeFirstLetter: Bool = false, numberOfUnits: Int? = nil) -> String {
let period:String = {
switch self {
case .day: return "day"
case .week: return "week"
case .month: return "month"
case .year: return "year"
}
}()
var numUnits = ""
var plural = ""
if let numberOfUnits = numberOfUnits {
numUnits = "\(numberOfUnits) " // Add space for formatting
plural = numberOfUnits > 1 ? "s" : ""
}
return "\(numUnits)\(capitalizeFirstLetter ? period.capitalized : period)\(plural)"
}
}
To use:
if #available(iOS 11.2, *),
let period = prod?.introductoryPrice?.subscriptionPeriod
{
let desc = period.unit.description(capitalizeFirstLetter: true, numberOfUnits: period.numberOfUnits)
} else {
// Fallback
}
This will create a nicely formatted string (e.g. 1 day, 1 Week, 2 months, 2 Years)
Upvotes: 12
Reputation: 521
I've solved it using DateComponentsFormatter, that saves you a lot of time localizing in different languages and handling plurals and whatnot. This might seem like a lot of code, but I hope it will save me time in the future.
import Foundation
class PeriodFormatter {
static var componentFormatter: DateComponentsFormatter {
let formatter = DateComponentsFormatter()
formatter.maximumUnitCount = 1
formatter.unitsStyle = .full
formatter.zeroFormattingBehavior = .dropAll
return formatter
}
static func format(unit: NSCalendar.Unit, numberOfUnits: Int) -> String? {
var dateComponents = DateComponents()
dateComponents.calendar = Calendar.current
componentFormatter.allowedUnits = [unit]
switch unit {
case .day:
dateComponents.setValue(numberOfUnits, for: .day)
case .weekOfMonth:
dateComponents.setValue(numberOfUnits, for: .weekOfMonth)
case .month:
dateComponents.setValue(numberOfUnits, for: .month)
case .year:
dateComponents.setValue(numberOfUnits, for: .year)
default:
return nil
}
return componentFormatter.string(from: dateComponents)
}
}
It requires to convert the SKProduct period unit into a NSCalendarUnit
import StoreKit
@available(iOS 11.2, *)
extension SKProduct.PeriodUnit {
func toCalendarUnit() -> NSCalendar.Unit {
switch self {
case .day:
return .day
case .month:
return .month
case .week:
return .weekOfMonth
case .year:
return .year
@unknown default:
debugPrint("Unknown period unit")
}
return .day
}
}
And you can call it from a SubscriptionPeriod like this:
import StoreKit
@available(iOS 11.2, *)
extension SKProductSubscriptionPeriod {
func localizedPeriod() -> String? {
return PeriodFormatter.format(unit: unit.toCalendarUnit(), numberOfUnits: numberOfUnits)
}
}
Which you can in turn call from a SKProductDiscount like so. Please note I didn't implement the other PaymentModes for now.
import StoreKit
@available(iOS 11.2, *)
extension SKProductDiscount {
func localizedDiscount() -> String? {
switch paymentMode {
case PaymentMode.freeTrial:
return "Free trial for \(subscriptionPeriod.localizedPeriod() ?? "a period")"
default:
return nil
}
}
}
Upvotes: 34
Reputation: 5400
Nice one @scott Wood. I would make it a property of SKProduct.PeriodUnit
instead of a function. That would keep the behaviour more consistent with enums:
@available(iOS 11.2, *)
extension SKProduct.PeriodUnit {
var description: String {
switch self {
case .day: return "day"
case .week: return "week"
case .month: return "month"
case .year: return "year"
// support for future values
default:
return "N/A"
}
}
func pluralisedDescription(length: Int) -> String {
let lengthAndDescription = length.description + " " + self.description
let plural = length > 1 ? lengthAndDescription + "s" : lengthAndDescription
return plural
}
}
And then a function to return the plural, based on the description
property.
And yes, as someone else pointed out, you should localise the plurals if your app is available in other languages.
Upvotes: 2
Reputation: 209
You can get it, but as mentioned above it works only starting from iOS 11.2, for other versions you'll have to get it from your server via API.
Here is an example code that I've used:
if #available(iOS 11.2, *) {
if let period = prod.introductoryPrice?.subscriptionPeriod {
print("Start your \(period.numberOfUnits) \(unitName(unitRawValue: period.unit.rawValue)) free trial")
}
} else {
// Fallback on earlier versions
// Get it from your server via API
}
func unitName(unitRawValue:UInt) -> String {
switch unitRawValue {
case 0: return "days"
case 1: return "weeks"
case 2: return "months"
case 3: return "years"
default: return ""
}
}
Upvotes: 20
Reputation: 4005
Starting from iOS 11.2 you can get info about trials using introductoryPrice
property of SKProduct
.
It contains instance of SKProductDiscount
class, which describes all discount periods including free trials.
Upvotes: 0
Reputation: 713
Trial length is not included in the SKProduct information and will have to be hardcoded into the app or stored on your server. The only available option for deriving this type of information (currently) is from the receipt itself.
Upvotes: 0