ウィジェットでBitbankの自分の資産を確認できるようにした


なぜ作ろうと思ったか

仮想通貨大好きな友だちから「ウィジェットで各通貨の値段は確認できるけど資産を確認できないのは不便」という声が。
ランチ1回で買収されて作ってみた。

↓は既存のビットバンクのウィジェット

確かに各コインの値段はわかるけど自分の資産情報はない

作ったもの

アプリ側で表示したいコインとBitbankのPrivateApiを叩くためのApiKey、ApiSecretを保存

それをウィジェットで表示するもの

1つのコインの資産を見たいとリクエストされたので今回は1つのコインの総資産、現在の価格を表示

Today Extension

iOSではこのウィジェットのことをToday Extensionと呼ぶ。
ターゲットの追加や実装方法などは他の方もあげているので一旦スルー
自分が詰まったところ中心に書きます。

ウィジェットのサイズを大きくするには

よくある表示を減らす、表示を増やすを押したら広がるもの。

TodayViewController.swift
    override func viewDidLoad() {
        super.viewDidLoad()
        self.extensionContext?.widgetLargestAvailableDisplayMode = NCWidgetDisplayMode.expanded
    }

widgetLargestAvailableDisplayModeを設定して表示を増やせるようにし、

TodayViewController.swift
    func widgetActiveDisplayModeDidChange(_ activeDisplayMode: NCWidgetDisplayMode, withMaximumSize maxSize: CGSize){
        if (activeDisplayMode == NCWidgetDisplayMode.compact) {
            self.preferredContentSize = maxSize;
        }
        else {
            self.preferredContentSize = CGSize(width: 0, height: 100);
        }
    }

このように増やした際の高さを指定。

どのタイミングでAPIを叩けばいいのか

TodayViewController.swift
    func widgetPerformUpdate(completionHandler: (@escaping (NCUpdateResult) -> Void)) {
        completionHandler(NCUpdateResult.newData)
    }

ウィジェットが表示されるたびにwidgetPerformUpdateが呼ばれる。(viewDidAppearてきな感じ。。?)
ここでAPI叩いた結果に対してNCUpdateResultに値を渡してあげる。

NCUpdateResultNewData - 内容が入れ替わったのでビューの再描画が必要
NCUpdateResultNoData - 更新は不要
NCUpdateResultFailed - 更新処理中にエラーが発生

UserDefaultsをアプリ間で共有

今回はアプリ側でコインとAPIの情報をUserDefaultsに保存。それをToday Extension側で読み込んでAPIを叩く必要があった。

TargetのCapabilitiesを選択
新しいApp Groupsを作成し、両方のTargetにそのApp Groupsを設定

このApp GroupsでUserDefaultsのインスタンスを作成し、保存、読み込めばデータが共有される。

let defaults = UserDefaults(suiteName: "group.共通のID")

リリースする際などはApple DeveloperからApp Groupsを作成して〜〜などしないといけないので注意

PrivateAPIを叩くためのHMAC-SHA256署名

個人的に今回一番詰まった。。
BitbankのProvateAPIを叩くには 「UNIXTIME、リクエストのパス、クエリパラメータ」 を連結させたものをAPI Secretと使ってHMAC-SHA256で署名しなくてはならない。

今回は以下のライブラリを使用

CryptoSwift

cocoapodsでインストール

pod 'CryptoSwift'

以下のように署名して使用

    private func makeAccessSignWith(accessKey: String, unixtime: String, path: String, queryParam: String) -> String? {
        let unixTimeStr = self.makeHaederUnixTime(unixTime: unixtime)
        let linkedStr = unixTimeStr + path + queryParam

        var bytes: [UInt8] = []

        bytes += linkedStr.bytes

        let signedString = try? HMAC(key: "APISecret", variant: .sha256).authenticate(bytes)

        return signedString?.toHexString()
    }

しかし叩けど認証エラーが返ってくる。。。
署名の作成方法が間違ってるのかと思っていると意外なとこで原因判明
BitBankのAPIドキュメントのページで自分のAPIキーとAPIシークレットを入力すると実際にAPIを叩いて確認することができるのだけれどそこのUNIXTIMEが

なぜ100年前。。。
この変なUNIXTIMEで一度APIを叩いたことが原因でその後も認証エラーがでてたよう。
クライアントから叩く際にUNIXTIMEに1000掛けして解決

今後

いまは一個のコインしか表示してないので全部表示して資産も全部足した資産にしよう