62
64

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

アプリ内課金アイテムの情報(SKProduct)を取得する

Last updated at Posted at 2015-04-08

価格情報はiTunesConnectから取得するのが推奨された方法です。
なので、課金アイテム情報(SKProduct)を取得するProductManagerなるクラスを作りました。
(最近、AppStore価格変更されましたし、ちょっとは役に立つかなと・・)

※実際の課金処理はこちらの記事で紹介してます。

ViewController.swift (使用例)
class ViewController: UIViewController {
    
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        //プロダクト情報取得
        ProductManager.request(productIdentifiers: productIdentifiers,
                               completion: {[weak self] (products: [SKProduct], error: Error?) -> Void in
                                guard error == nil else { return }
                                
                                for product in products {
                                    //価格を抽出
                                    let priceString = product.localizedPrice ?? "--"
                                    /*
                                     TODO: UI更新
                                     
                                     
                                     
                                     
                                     
                                     */
                                }
                                
        })
}

※Swift2.0でwarning出たので、、修正しました。。

ProductManager.swift
import Foundation
import StoreKit


/// 価格情報取得エラー
public enum ProductManagerError: Error {
    case emptyProductIdentifiers
    case noValidProducts
    case notMatchProductIdentifier
    case skError(messaga: String)
    case unkown
    
    public var localizedDescription: String {
        switch self {
        case .emptyProductIdentifiers:
            return "プロダクトIDが指定されていません。"
        case .noValidProducts:
            return "有効なプロダクトを取得できませんでした。"
        case .notMatchProductIdentifier:
            return "指定したプロダクトIDと取得したプロダクトIDが一致していません。"
        case .skError(let message):
            return message
        default:
            return "不明なエラー"
        }
    }
}


/// 価格情報を取得するためのクラス
final public class ProductManager: NSObject {
    ///保持用
    static private var managers: Set<ProductManager> = Set()
    
    ///完了通知
    public typealias Completion = ([SKProduct], Error?) -> Void
    
    ///完了通知
    public typealias CompletionForSingle = (SKProduct?, Error?) -> Void
    
    ///完了通知用
    private var completion: Completion

    ///価格問い合わせ用オブジェクト(保持用)
    private var productRequest: SKProductsRequest?
    
    ///初期化
    private init(completion: @escaping Completion) {
        self.completion = completion
    }
    
    /// 課金アイテム情報を取得(複数)
    ///
    /// - Parameters:
    ///   - productIdentifiers: プロダクトID配列
    ///   - completion: 課金アイテム情報取得完了時の処理
    class func request(productIdentifiers: [String], completion: @escaping Completion) {
        guard !productIdentifiers.isEmpty else {
            completion([], ProductManagerError.emptyProductIdentifiers)
            return
        }
        
        let productManager = ProductManager(completion: completion)
        let productRequest = SKProductsRequest(productIdentifiers: Set(productIdentifiers))
        productRequest.delegate = productManager
        productRequest.start()
        productManager.productRequest = productRequest
        managers.insert(productManager)
    }
    
    /// 課金アイテム情報を取得(1つ)
    ///
    /// - Parameters:
    ///   - productIdentifier: プロダクトID
    ///   - completion: 課金アイテム情報取得完了時の処理
    class func request(productIdentifier: String, completion: @escaping CompletionForSingle) {
        ProductManager.request(productIdentifiers: [productIdentifier]) { (products, error) in
            guard error == nil else {
                completion(nil, error)
                return
            }
            
            guard let product = products.first else {
                completion(nil, ProductManagerError.noValidProducts)
                return
            }
            
            guard product.productIdentifier == productIdentifier else {
                completion(nil, ProductManagerError.notMatchProductIdentifier)
                return
            }
            
            completion(product, nil)
        }
    }    
}

// MARK: - SKProducts Request Delegate
extension ProductManager: SKProductsRequestDelegate{
    public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
        let error = !response.products.isEmpty ? nil : ProductManagerError.noValidProducts
        completion(response.products, error)
    }
    
    public func request(_ request: SKRequest, didFailWithError error: Error) {
        completion([], ProductManagerError.skError(messaga: error.localizedDescription))
        ProductManager.managers.remove(self)
    }
    
    public func requestDidFinish(_ request: SKRequest) {
        ProductManager.managers.remove(self)
    }
}


// MARK: - Utility
public extension SKProduct {
    /// 価格
    var localizedPrice: String? {
        let numberFormatter = NumberFormatter()
        numberFormatter.formatterBehavior = .behavior10_4
        numberFormatter.numberStyle = .currency
        numberFormatter.locale = priceLocale
        return numberFormatter.string(from: price)
    }
}

ちなみに、Appleのサンプルコード(StoreManger)では、

・シングルトン
・取得完了通知はNSNotificationCenter
・SKProductを保持

という実装になってます。(どれもかぶってない・・・)
価格情報を静かに更新するような場合は、NSNotificationCenterでいいかなぁとも思います。が、
価格情報今から読み込みます!読み込み完了しました!的なことをしたい場合は、ブロックスがいいかなぁとも思います。
用途と好みに応じて使い分ければいいかなと^^;

※Objective-Cで書いてたのをSwiftで書き直したのですが、、
間違ってたら、、、優しくご指導いただければ幸いです。。

62
64
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
62
64

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?