しめ鯖日記

swift, iPhoneアプリ開発, ruby on rails等のTipsや入門記事書いてます

fastlaneで複数の国のメタデータを一括更新

アプリの申請や証明書周りを自動化するfastlaneを試してみました。

インストール

インストールはgemを利用します。

gem install fastlane

fastlaneの初期化

インストールが終わったらfastlaneの初期化を行います。
プロジェクトのフォルダで以下コマンドを実行します。

fastlane init

そうするとAppleIDを聞かれるので答えます。

f:id:llcc:20161222222647p:plain

今回は既にあるアプリに適用したので、iTunesconnectからメタデータをダウンロードしています。

f:id:llcc:20161222222832p:plain

ダウンロードしたファイルはfastlaneフォルダ以下に入ります。

f:id:llcc:20161222223028p:plain

fastlaneでメタデータをアップロード

fastlaneを使ったメタデータの更新を試してみます。
fastlane/metadata/en-USフォルダのrelease_notes.txtを更新します。

f:id:llcc:20161222225222p:plain

次にプロジェクトのルートフォルダで以下コマンドを打ちます。

fastlane deliver

すると以下のようにアップロードを開始してくれます。

f:id:llcc:20161222225417p:plain

iTunesconnectにアクセスすると先程更新したReleaseNoteが反映されている事が分かります。
ファイルを用意するだけでメタデータの更新ができるので、複数国対応しているアプリのリリースも非常に楽になりそうです。

f:id:llcc:20161222230036p:plain

【Swift】Chartsを使って色々なグラフを描画してみる

Chartsというライブラリを試して色々なグラフを使ってみました。

github.com

インストール

CocoaPodsでインストールしました。

target 'MyApp' do
  use_frameworks!

  pod "Charts"
end

棒グラフ

import UIKit
import Charts

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        var rect = view.bounds
        rect.origin.y += 20
        rect.size.height -= 20
        let barChartView = BarChartView(frame: rect)
        let entry = [
            BarChartDataEntry(x: 10, y: 30),
            BarChartDataEntry(x: 20, y: 20),
            BarChartDataEntry(x: 30, y: 40),
            BarChartDataEntry(x: 40, y: 10),
            BarChartDataEntry(x: 50, y: 30)
        ]
        let set = [
            BarChartDataSet(values: entry, label: "Data")
        ]
        barChartView.data = BarChartData(dataSets: set)
        view.addSubview(barChartView)
    }
}

f:id:llcc:20161219231209p:plain

バブルチャート

import UIKit
import Charts

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        var rect = view.bounds
        rect.origin.y += 20
        rect.size.height -= 20
        let chartView = BubbleChartView(frame: rect)
        let entries = [
            BubbleChartDataEntry(x: 1, y: 10, size: 30),
            BubbleChartDataEntry(x: 2, y: 60, size: 10),
            BubbleChartDataEntry(x: 3, y: 20, size: 20),
            BubbleChartDataEntry(x: 4, y: 40, size: 30),
            BubbleChartDataEntry(x: 5, y: 30, size: 40),
            BubbleChartDataEntry(x: 6, y: 10, size: 20)
        ]
        let set = BubbleChartDataSet(values: entries)
        chartView.data = BubbleChartData(dataSet: set)
        view.addSubview(chartView)
    }
}

f:id:llcc:20161219231744p:plain

ロウソク足

import UIKit
import Charts

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        var rect = view.bounds
        rect.origin.y += 20
        rect.size.height -= 20
        let chartView = CandleStickChartView(frame: rect)
        let entries = [
            CandleChartDataEntry(x: 1, shadowH: 12, shadowL: 10, open: 11, close: 11),
            CandleChartDataEntry(x: 2, shadowH: 20, shadowL: 15, open: 18, close: 17),
            CandleChartDataEntry(x: 3, shadowH: 30, shadowL: 24, open: 26, close: 28),
        ]
        let set = CandleChartDataSet(values: entries, label: "Test")
        chartView.data = CandleChartData(dataSet: set)
        view.addSubview(chartView)
    }
}

f:id:llcc:20161219232244p:plain

横棒グラフ

データは棒グラフ同様、BarChartDataを使います。

import UIKit
import Charts

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        var rect = view.bounds
        rect.origin.y += 20
        rect.size.height -= 20
        let chartView = HorizontalBarChartView(frame: rect)
        let entry = [
            BarChartDataEntry(x: 1, y: 30),
            BarChartDataEntry(x: 2, y: 20),
            BarChartDataEntry(x: 3, y: 40),
            BarChartDataEntry(x: 4, y: 10),
            BarChartDataEntry(x: 5, y: 30)
        ]
        let set = BarChartDataSet(values: entry, label: "Data")
        chartView.data = BarChartData(dataSet: set)
        view.addSubview(chartView)
    }
}

f:id:llcc:20161219232913p:plain

折れ線グラフ

import UIKit
import Charts

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        var rect = view.bounds
        rect.origin.y += 20
        rect.size.height -= 20
        let chartView = LineChartView(frame: rect)
        let entries = [
            BarChartDataEntry(x: 1, y: 30),
            BarChartDataEntry(x: 2, y: 20),
            BarChartDataEntry(x: 3, y: 40),
            BarChartDataEntry(x: 4, y: 10),
            BarChartDataEntry(x: 5, y: 30)
        ]
        let set = LineChartDataSet(values: entries, label: "Data")
        chartView.data = LineChartData(dataSet: set)
        view.addSubview(chartView)
    }
}

f:id:llcc:20161219233138p:plain

円グラフ

import UIKit
import Charts

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        var rect = view.bounds
        rect.origin.y += 20
        rect.size.height -= 20
        let chartView = PieChartView(frame: rect)
        let entries = [
            PieChartDataEntry(value: 10, label: "A"),
            PieChartDataEntry(value: 20, label: "B"),
            PieChartDataEntry(value: 30, label: "C"),
            PieChartDataEntry(value: 40, label: "D"),
            PieChartDataEntry(value: 50, label: "E")
        ]
        let set = PieChartDataSet(values: entries, label: "Data")
        chartView.data = PieChartData(dataSet: set)
        view.addSubview(chartView)
    }
}

f:id:llcc:20161219233402p:plain

レーダーチャート

import UIKit
import Charts

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        var rect = view.bounds
        rect.origin.y += 20
        rect.size.height -= 20
        let chartView = RadarChartView(frame: rect)
        let entries = [
            RadarChartDataEntry(value: 40),
            RadarChartDataEntry(value: 30),
            RadarChartDataEntry(value: 20),
            RadarChartDataEntry(value: 40),
        ]
        let set = RadarChartDataSet(values: entries, label: "Data")
        chartView.data = RadarChartData(dataSet: set)
        view.addSubview(chartView)
    }
}

f:id:llcc:20161219233623p:plain

点グラフ

import UIKit
import Charts

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        var rect = view.bounds
        rect.origin.y += 20
        rect.size.height -= 20
        let chartView = ScatterChartView(frame: rect)
        let entries = [
            BarChartDataEntry(x: 1, y: 10),
            BarChartDataEntry(x: 2, y: 15),
            BarChartDataEntry(x: 3, y: 9),
            BarChartDataEntry(x: 4, y: 13),
        ]
        
        let set = ScatterChartDataSet(values: entries, label: "Data")
        chartView.data = ScatterChartData(dataSet: set)
        view.addSubview(chartView)
    }
}

f:id:llcc:20161219233942p:plain

まとめ

今回は簡単に一通りのグラフに触れてみました。
どれも同じようなインターフェースな為、一つ実装すれば他のものも同じように作れそうです。

【iOS】指紋認証を使ってみる

iPhone5sから登場した指紋認証ですが、まだちゃんと使った事がなかったので試してみました。

指紋認証可能化どうかはLAContextクラスのcanEvaluatePolicyメソッドを使います。 LAContextを使うためにはLocalAuthenticationをimportする必要があります。

import LocalAuthentication

class ViewController: UIViewController {
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        let context = LAContext()
        var error: NSError?
        if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
            // 指紋認証可能
        }
    }
}

認証はevaluatePolicyメソッドを使います。
引数には認証画面での説明テキストも入れます。

import LocalAuthentication

class ViewController: UIViewController {
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        let context = LAContext()
        var error: NSError?
        if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
            context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "パスコードロックに使う") { success, error in
                
            }
        }
    }
}

起動すると以下のようにパスコードを求められる画面になります。

f:id:llcc:20161218114101p:plain

.deviceOwnerAuthenticationWithBiometricsでなく.deviceOwnerAuthenticationを使った場合

シミュレータで試したところ以下のようにパスコードを求められました。

f:id:llcc:20161218114448p:plain

【iOS】Smile-Lockでパスコードロック機能を実現する

Smile-Lockというライブラリを試してみました。
URL見るとリクルートライフスタイルで出しているライブラリなんですね。

github.com

インストール

CocoaPodsでインストールしました。

target 'MyApp' do
  use_frameworks!

  pod 'SmileLock'
end

使い方

下のようにWindowやViewに貼り付ける事で利用します。

import UIKit
import SmileLock

class ViewController: UIViewController {
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        let kPasswordDigit = 6
        let contentView = PasswordContainerView.createWithDigit(kPasswordDigit)
        if let window = view.window {
            let baseView = UIView(frame: window.bounds)
            baseView.addSubview(contentView)
            contentView.center = baseView.center
            window.addSubview(baseView)
        }
    }
}

実行すると下のような表示になります。

f:id:llcc:20161217214232p:plain

パスコードを入力した時の動作はプロトコルに記述します。
passwordInputComplete:input:は入力が完了した時に呼ばれます。

extension ViewController: PasswordInputCompleteProtocol {
    func passwordInputComplete(_ passwordContainerView: PasswordContainerView, input: String) {
        print(input)
    }
    
    func touchAuthenticationComplete(_ passwordContainerView: PasswordContainerView, success: Bool, error: NSError?) {
        
    }
}

パスコードが違う場合のシェイクはwrongPasswordメソッドを使います。
入力のクリアだけしたい場合はclearInputメソッドを使えば良さそうです。

extension ViewController: PasswordInputCompleteProtocol {
    func passwordInputComplete(_ passwordContainerView: PasswordContainerView, input: String) {
        passwordContainerView.wrongPassword()
    }
    
    func touchAuthenticationComplete(_ passwordContainerView: PasswordContainerView, success: Bool, error: NSError?) {
        
    }
}

それとPasswordContainerViewのプロパティーを触る事で少しだけデザインを変更できます。
ボタンの色を変えるtintColor、ハイライト時の色を変えるhighlightedColor、曇りガラス風デザインに変更できるisVibrancyEffectなどがあります。

f:id:llcc:20161217220112p:plain

タッチIDが使える場合は自動的にViewにタッチボタンが表示されます。

f:id:llcc:20161217220703p:plain

押すとタッチIDを求める表示になります。

f:id:llcc:20161217220830p:plain

指紋認証が完了するとdelegateのtouchAuthenticationCompleteメソッドが呼ばれます。

extension ViewController: PasswordInputCompleteProtocol {
    func passwordInputComplete(_ passwordContainerView: PasswordContainerView, input: String) {
    }
    
    func touchAuthenticationComplete(_ passwordContainerView: PasswordContainerView, success: Bool, error: NSError?) {
        print(success)
    }
}

今回は触れないのですが、PasswordUIValidationというクラスを使ったアプローチもあるようです。

THPinViewControllerとの比較

THPinViewControllerより自由度が高いのが良さそうでした。
THPinViewControllerは良く使っているんですが、「パスコード正解時は自動的にdissmissされる為、確認ページへ遷移させるのが難しい」みたいな問題があってそれが解決するだけでも良さそうです。
それと指紋認証を標準で対応してくれているのもありがたいです。

www.cl9.info

FirebaseRemoteConfigでA/Bテストを試してみる

Firebaseを使う準備

まずは下サイトで新しいプロジェクトを作成します。

Firebase Console

f:id:llcc:20161215213452p:plain

プロジェクトを作成すると以下のような画面になるのでiOS アプリに Firebase を追加を選びます。

f:id:llcc:20161215213525p:plain

情報を入力してアプリを追加します。

f:id:llcc:20161215213609p:plain

次画面に移動するとGoogleService-Info.plistというファイルのダウンロードが開始します。
このファイルは後で使います。

f:id:llcc:20161215213643p:plain

次はXcodeでプロジェクトを作成します。
SingleViewApplicationを選択します。

f:id:llcc:20161215213858p:plain

次にFirebaseをインストールします。
Podsファイルを作成して以下のように修正して下さい。

platform :ios, ’10.0’

target 'MyApp' do
  use_frameworks!

  pod 'Firebase/Core'
  pod 'Firebase/RemoteConfig'
end

先程ダウンロードしたGoogleService-Info.plistをプロジェクトに追加します。

f:id:llcc:20161215214019p:plain

最後にAppDelegate.swiftへFirebase初期化処理を追加します。
これでFirebaseを使えるようになりました。

import UIKit
import Firebase

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        
        FIRApp.configure()
        
        return true
    }
}

FirebaseRemoteConfigを使ってみる

続けてFirebaseRemoteConfigを使ってみます。
Firebaseの管理画面に戻り、左メニューのRemote Configを選択します。

f:id:llcc:20161215214415p:plain

そのページで最初のパラメータを追加ボタンを押してキーと値を追加します。

f:id:llcc:20161215214524p:plain

最後に変更を公開ボタンを押して追加したパラメータを有効化します。

f:id:llcc:20161215214834p:plain

設定したパラメータの取得方法は以下の通りです。

import UIKit
import Firebase

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    let remoteConfig = FIRRemoteConfig.remoteConfig()

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        
        FIRApp.configure()
        remoteConfig.fetch { [weak self] status, error in
            print(status.rawValue)
            print(error)
            self?.remoteConfig.activateFetched() // これでパラメータを取得できる
            print(self?.remoteConfig["test_key"].stringValue) // → "test_value"
        }
        
        return true
    }
}

FirebaseRemoteConfigでA/Bテストをする

最後にFirebaseRemoteConfigを使ったA/Bテストの方法を見ていきます。
まず管理画面上の条件を追加ボタンから条件を追加します。

f:id:llcc:20161215223617p:plain

条件はUser in random percentileにして、好きな割合を入力します。

f:id:llcc:20161215223714p:plain

保存すると、2種類の値を設定できるようになります。
これらは先程設定した割合に応じて出しわけられます。

f:id:llcc:20161215223823p:plain

余談ですが、条件は推定地域などを選ぶ事ができます。
国に応じて出し分けをしたい場合などに有効そうです。

f:id:llcc:20161215223914p:plain

最後にFirebaseAnalyticsの設定を行います。
サイドメニューのAnalyticsタブのユーザープロパティーを選択して下さい。

f:id:llcc:20161215224125p:plain

新しいユーザーのプロパティーボタンからプロパティーをセットします。

f:id:llcc:20161215224240p:plain

次はXcode上でユーザーのプロパティーをセットします。
FIRAnalytics.setUserPropertyStringを使ってプロパティーのセットを行って下さい。

import UIKit
import Firebase

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    let remoteConfig = FIRRemoteConfig.remoteConfig()

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        
        FIRApp.configure()
        remoteConfig.fetch { [weak self] status, error in
            self?.remoteConfig.activateFetched()
            FIRAnalytics.setUserPropertyString(self?.remoteConfig["test_key"].stringValue, forName: "my_property")
        }
        
        return true
    }
}

これで実装は完了です。
Analytics上でプロパティー毎の数字を見る事ができるので、これを使って値の出し分けの成果を見ます。

f:id:llcc:20161215224559p:plain

その他

FirebaseRemoteConfigはとてもおもしろかったのですが、コホート分析でプロパティーによるフィルターが使えないのが少し痛かったです。
アクティブ率をKPIにする事が多いので、ここでA/Bテストを多用したかったです。

f:id:llcc:20161215224837p:plain

この辺りはGoogleAnalyticsのカスタムディメンションを使ったほうがうまくいくかもしれません。
GoogleAnalyticsだと下のように見ることができます。

f:id:llcc:20161221103050p:plain

参考URL

Firebase Remote Config  |  Firebase

R.swiftでNSLocalizedStringも取れるようになっていたので試してみる

R.swiftがいつの間にかNSLocalizedStringにも対応されていたので動かしてみました。

R.swiftとは

R.swiftとはStoryboardや画像などの取得を便利にするライブラリです。
Storyboardの取得はUIStoryboard(name: "Main", bundle: nil)のように文字列で指定しますが、R.swiftを使うとR.storyboard.mainと変数として取得する事ができます。
以前記事を書いたので、宜しければ併せてご参照下さい。

R.swiftを使ってStoryboard名や画像名のTypoを0にする - Qiita

準備

CocoaPodsを使ってインストールします。

platform :ios, ’10.0’

target 'MyApp' do
  use_frameworks!

  pod “R.swift”
end

次にBuild PhasesでRun scriptを追加して"$PODS_ROOT/R.swift/rswift" "$SRCROOT"と入力します。

f:id:llcc:20161214234041p:plain

この状態ビルドすればR.generated.swiftが生成されるので、プロジェクトに追加します。

f:id:llcc:20161214234158p:plain

これでRという構造体を通してStoryboardなどを取得できるようになりました。

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        R.storyboard.main.instantiateInitialViewController()
    }
}

NSLocalizedStringを取得してみる

MyString.stringsを追加して、以下のようにテキストを記入しました。

"MyText" = "Hello!";

ビルドするとR.generated.swiftに先程の文字が追加されていました。

print(R.string.myString.myText()) // → Hello!

MyString2.stringsファイルを作って、"myText2" = "Hello world!";を追加したら以下のようになりました。

print(R.string.myString.myText()) // → Hello!
print(R.string.myString2.myText2()) // → Hello world!

複数言語対応も試してみます。
MyString.stringsを英語用と日本語用を作って、英語用には"MyText" = "Hello!";、日本語用には"MyText" = "こんにちは";を追加します。

f:id:llcc:20161214235227p:plain

この状態で起動すると、シミュレータの言語設定に合わせた文字を出力してくれます。

print(R.string.myString.myText()) // → Hello! or こんにちは

【Swift】WKWebViewのスクロール速度を変更する

WKWebViewのスクロール速度を変更する方法を調べてみました。 まずはViewControllerにwebViewを貼り付けます。

import UIKit
import WebKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let webView = WKWebView(frame: view.bounds)
        webView.load(URLRequest(url: URL(string: "https://google.com")!))
        view.addSubview(webView)
    }
}

起動するとSafariなどと同様のスクロール速度です。

f:id:llcc:20161213230103g:plain

webViewのscrollViewのdecelerationRateにUIScrollViewDecelerationRateNormalをセットします。

import UIKit
import WebKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let webView = WKWebView(frame: view.bounds)
        webView.load(URLRequest(url: URL(string: "https://google.com")!))
        webView.scrollView.decelerationRate = UIScrollViewDecelerationRateNormal // 追加
        view.addSubview(webView)
    }
}

これで起動すると、スクロールがかなり早くなります。

f:id:llcc:20161213230527g:plain

スクロール速度はUIScrollViewDecelerationRateFastとUIScrollViewDecelerationRateNormalが用意されています。

f:id:llcc:20161213230129p:plain

decelerationRateはCGFloatなので直接値を指定する事もできます。
ちなみに0.1だと指を話した瞬間に止まるような動きになります。

webView.scrollView.decelerationRate = 0.1

それぞれの値は下のようになっていました。

print(webView.scrollView.decelerationRate) // 0.9893243312
print(UIScrollViewDecelerationRateNormal) // 0.998
print(UIScrollViewDecelerationRateFast) // 0.99

参考URL

UIWebViewのスクロールを早くしたい! - Qiita