iOS in-app purchase subscription get free trial period length from SKProduct

11,430

Solution 1

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
        }
    }
}

Solution 2

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 ""
    }
}

Solution 3

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)

Solution 4

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.

Share:
11,430

Related videos on Youtube

mjpablo23
Author by

mjpablo23

learning iOS programming

Updated on July 27, 2022

Comments

  • mjpablo23
    mjpablo23 almost 2 years

    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?

    • Paulw11
      Paulw11 over 6 years
      No. You can get information from the receipt that indicates whether the subscription is currently in the free trial period, but not the free trial length
    • mjpablo23
      mjpablo23 over 6 years
      ah ok, thanks. got it.
  • vmeyer
    vmeyer over 5 years
    Great answer. If you want to localise, use stringdicts instead of plural variable : developer.apple.com/library/archive/documentation/MacOSX/…
  • Niels Mouthaan
    Niels Mouthaan over 3 years
    The most elegant solution as it properly takes into account localization.