2016年11月30日水曜日

はじめてのTensorFlow 「Variables: 生成、初期化、保存と復元」その3

原文:
Variables: Creation, Initialization, Saving, and Loading
https://www.tensorflow.org/versions/r0.12/how_tos/variables/index.html#variables-creation-initialization-saving-and-loading


Saving and Restoring

モデルの保存/復元はtf.train.Saverオブジェクトを使用してください。コンストラクタはすべてのグラフ操作、特定のリスト、グラフ内の変数の保存/復元が可能です。Saverオブジェクトは、これらのopsを実行するメソッドを提供し、チェックポイントファイルの書き込みまたは読み取りのパスを指定します。


Checkpoint Files

変数はバイナリファイルに保存され、大まかに、変数名からテンソル値までのマップを含みます。
Saverオブジェクトを作成するときは、チェックポイントファイル内の変数の名前を任意に選択できます。 デフォルトでは、変数ごとにVariable.nameプロパティの値が使用されます。

チェックポイント内の変数を理解するには、inspect_checkpointライブラリ、特にprint_tensors_in_checkpoint_file関数を使用します。


Saving Variables

tf.train.Saver()を使用してSaverを作成し、モデル内のすべての変数を管理します。

# Create some variables.
v1 = tf.Variable(..., name="v1")
v2 = tf.Variable(..., name="v2")
...
# Add an op to initialize the variables.
init_op = tf.global_variables_initializer()

# Add ops to save and restore all the variables.
saver = tf.train.Saver()

# Later, launch the model, initialize the variables, do some work, save the
# variables to disk.
with tf.Session() as sess:
  sess.run(init_op)
  # Do some work with the model.
  ..
  # Save the variables to disk.
  save_path = saver.save(sess, "/tmp/model.ckpt")
  print("Model saved in file: %s" % save_path)


Restoring Variables

同じSaverオブジェクトが変数の復元に使用されます。 ファイルから変数を復元するときは、あらかじめそれらを初期化する必要はありません。


# Create some variables.
v1 = tf.Variable(..., name="v1")
v2 = tf.Variable(..., name="v2")
...
# Add ops to save and restore all the variables.
saver = tf.train.Saver()

# Later, launch the model, use the saver to restore variables from disk, and
# do some work with the model.
with tf.Session() as sess:
  # Restore variables from disk.
  saver.restore(sess, "/tmp/model.ckpt")
  print("Model restored.")
  # Do some work with the model
  ...


Choosing which Variables to Save and Restore

tf.train.Saver()に引数を渡さない場合、グラフのすべての変数がSaverによって処理されます。 それぞれの変数は、変数の作成時に渡された名前で保存されます。
チェックポイントファイル内の変数の名前を明示的に指定すると便利なことがあります。 たとえば、 "weights"という名前の変数を持つモデルを訓練し、その値を "params"という名前の新しい変数にリストアすることができます。

また、モデルによって使用される変数のサブセットの保存または復元のみが有用な場合もあります。 たとえば、5層のニューラルネットを訓練して、6層の新しいモデルを訓練し、以前訓練されたモデルの5つの層のパラメータを新しいモデルの最初の5つの層に復元する必要があります。

tf.train.Saver()コンストラクタにPythonのdictionaryを渡すことで、保存する名前と変数を簡単に指定できます。keysは使用する名前、valueは管理する変数です。

ノート:
モデル変数の異なるサブセットを保存して復元する必要がある場合は、必要な数のセーバーオブジェクトを作成できます。 同じ変数を複数の保存オブジェクトにリストすることができます。その値は、saver restore()メソッドが実行されたときにのみ変更されます。

詳細は、tf.initialize_variablesを参照してください。

# Create some variables.
v1 = tf.Variable(..., name="v1")
v2 = tf.Variable(..., name="v2")
...
# Add ops to save and restore only 'v2' using the name "my_v2"
saver = tf.train.Saver({"my_v2": v2})
# Use the saver object normally after that.
...

はじめてのTensorFlow 「Variables: 生成、初期化、保存と復元」その2

原文:
Variables: Creation, Initialization, Saving, and Loading
https://www.tensorflow.org/versions/r0.12/how_tos/variables/index.html#variables-creation-initialization-saving-and-loading


Initialization

変数の初期化は、他の操作を行う前に明確に実行する必要があります。モデルを使用する前にすべての変数を初期化を実行して下さい。

チェックポイントファイルから変数の値を復元することもできます(以下を参照)
変数を初期化するには、tf.global_variables_initializer()を使用します。
モデルを完全に生成してセッションを起動した後にのみ、そのオペレーションを実行してください。


# Create two variables.
weights = tf.Variable(tf.random_normal([784, 200], stddev=0.35),
                      name="weights")
biases = tf.Variable(tf.zeros([200]), name="biases")
...
# Add an op to initialize the variables.
init_op = tf.global_variables_initializer()

# Later, when launching the model
with tf.Session() as sess:
  # Run the init operation.
  sess.run(init_op)
  ...
  # Use the model
  ...


Initialization from another Variable

他の変数の初期値で変数を初期化することも可能です。tf.global_variables_initializerによって追加された演算子はすべての変数を並列に初期化するので、必要なときは注意して下さい。

他の変数の値から変数を初期化は変数のinitialized_value()プロパティを使います。初期化された値は、新しい変数の初期値として直接使用することも、他のテンソルとして使用して新しい変数の値を計算することもできます。


# Create a variable with a random value.
weights = tf.Variable(tf.random_normal([784, 200], stddev=0.35),
                      name="weights")
# Create another variable with the same value as 'weights'.
w2 = tf.Variable(weights.initialized_value(), name="w2")
# Create another variable with twice the value of 'weights'
w_twice = tf.Variable(weights.initialized_value() * 2.0, name="w_twice")



Custom Initialization

tf.global_variables_initializerは、モデル内のすべての変数を初期化するための演算子を追加します。 また、初期化する変数の明示的なリストを渡すこともできます。 変数が初期化されているかどうかのチェックなど、その他のオプションについては変数ドキュメントを参照してください。

はじめてのTensorFlow 「Variables: 生成、初期化、保存と復元」その1

原文:
Variables: Creation, Initialization, Saving, and Loading
https://www.tensorflow.org/versions/r0.12/how_tos/variables/index.html#variables-creation-initialization-saving-and-loading


Variables: 生成、初期化、保存と復元

モデルを列挙にする際、パラメータを保持、更新するためにVariables(以後、変数)を使います。Variablesは行列としてメモリ上に存在します。変数は明示的に初期化する必要があり、トレーニング中かつトレーニング後はディスク上に可能です。保存された値を後で復元して、モデルを実行または分析することができます。

このドキュメントでは、次のTensorFlowクラスを参照しています。 APIの完全な説明についてはリファレンスマニュアルのリンクをクリックしてください:
The tf.Variable class.
The tf.train.Saver class.


Creation

変数を生成する際、初期値としてTensrに値をVariable()コンストラクタに渡します。
TensorFlowは、定数やランダムな値から初期化するためによく使われるテンソルを生成するopsのコレクションを提供します。

これらすべての操作では、テンソルの形状を指定する必要があります。 その形状は自動的に変数の形になります。 変数は一般的に固定された形状を持ちますが、TensorFlowは変数を再構成する高度なメカニズムを提供します。

# Create two variables.
weights = tf.Variable(tf.random_normal([784, 200], stddev=0.35),
                      name="weights")
biases = tf.Variable(tf.zeros([200]), name="biases")

tf.Variable()をコールすると、グラフにopsが追加されます。
・保持:値を保持する
・初期化:変数を初期化する。実際にはtf.assignで操作する。
・初期値:biases変数のゼロ初期化など。この例ではグラフに追加もしている

tf.Variable()の値が返す値は、Pythonクラスtf.Variableのインスタンスです。



Device placement

変数は、生成するときに特定のデバイスに固定することができます。tf.deviceを使用します。

# Pin a variable to CPU.
with tf.device("/cpu:0"):
  v = tf.Variable(...)

# Pin a variable to GPU.
with tf.device("/gpu:0"):
  v = tf.Variable(...)

# Pin a variable to a particular parameter server task.
with tf.device("/job:ps/task:7"):
  v = tf.Variable(...)

変数をmutateする操作、v.assign()やtf.train.Optimizer内でパラメータを更新する操作は同じデバイス上で実行する必要があります。互換性のないデバイス配置指定は、これらの操作を生成するときに無視されます。

複製された設定で実行する場合、デバイス配置は特に重要です。
複製されたされたモデルのデバイス設定を簡素化できるデバイス機能の詳細は、tf.train.replica_device_setterを参照してください。

2016年11月29日火曜日

はじめてのTensorFlow PyCharm編

PyCharmを起動して新しいプロジェクオを作成、interpreterでtensoflowの環境を選択します。



プロジェクト作成に成功すると、External LibrariesにTensorFlowに必要なライブラリが入った状態になります。
TensorFlow用のpyファイルを作成して、コーディングします。



Runを押して、プログラムを実行。


実行結果はPyCharm上で確認できます。Terminalで実行するより、PyCharmなどのIDEで開発する方が効率が良いです。

2016年9月25日日曜日

はじめてのTensorFlow 動作確認編

r.0.10の動作確認用のソースコードが用意されています。
インストール後に実行してみましょう。
https://www.tensorflow.org/versions/r0.10/get_started/index.html


動作確認

動作確認用のPythonファイルを作成します。
(tensorflow)$ touch tf_test.py
(tensorflow)$ vi tf_test.py


サンプルソースはy = x * a + bの、aとbの値を求めるプログラムです。
import tensorflow as tf
import numpy as np

# Create 100 phony x, y data points in NumPy, y = x * 0.1 + 0.3
x_data = np.random.rand(100).astype(np.float32)
y_data = x_data * 0.1 + 0.3

# Try to find values for W and b that compute y_data = W * x_data + b
# (We know that W should be 0.1 and b 0.3, but TensorFlow will
# figure that out for us.)
W = tf.Variable(tf.random_uniform([1], -1.0, 1.0))
b = tf.Variable(tf.zeros([1]))
y = W * x_data + b

# Minimize the mean squared errors.
loss = tf.reduce_mean(tf.square(y - y_data))
optimizer = tf.train.GradientDescentOptimizer(0.5)
train = optimizer.minimize(loss)

# Before starting, initialize the variables.  We will 'run' this first.
init = tf.initialize_all_variables()

# Launch the graph.
sess = tf.Session()
sess.run(init)

# Fit the line.
for step in range(201):
    sess.run(train)
    if step % 20 == 0:
        print(step, sess.run(W), sess.run(b))

# Learns best fit is W: [0.1], b: [0.3]


プログラムの実行。
(tensorflow)$ python tf_test.py
(0, array([-0.08499005], dtype=float32), array([ 0.5096584], dtype=float32))
(20, array([ 0.03381674], dtype=float32), array([ 0.33253124], dtype=float32))
(40, array([ 0.08111919], dtype=float32), array([ 0.30928054], dtype=float32))
(60, array([ 0.09461367], dtype=float32), array([ 0.30264756], dtype=float32))
(80, array([ 0.09846338], dtype=float32), array([ 0.30075532], dtype=float32))
(100, array([ 0.09956162], dtype=float32), array([ 0.30021548], dtype=float32))
(120, array([ 0.09987493], dtype=float32), array([ 0.30006149], dtype=float32))
(140, array([ 0.09996434], dtype=float32), array([ 0.30001754], dtype=float32))
(160, array([ 0.09998985], dtype=float32), array([ 0.30000502], dtype=float32))
(180, array([ 0.09999709], dtype=float32), array([ 0.30000144], dtype=float32))
(200, array([ 0.09999915], dtype=float32), array([ 0.30000043], dtype=float32))

0.1 と 0.3の近似値が算出されている!!

はじめてのTensorFlow インストール編

TensorFlow

以下、公式ページを参考にしました。
https://www.tensorflow.org/versions/master/get_started/os_setup.html#virtualenv-installation

記事の動作確認環境はr.0.10、MacOS 10.11.6です。


ここで使う知識

pip
https://ja.wikipedia.org/wiki/Pip
Virtualenv
https://virtualenv.pypa.io/en/stable/


TensorFlowの環境構築

所要時間5分以内で完了します。

pipとVirtualenvのインストール
$ sudo easy_install pip
$ sudo pip install --upgrade virtualenv
Virtualenvの環境を構築する。ディレクトリは~/tensorflowを指定。
$ virtualenv --system-site-packages ~/tensorflow
Virtualenvで構築した環境に入る。
$ source ~/tensorflow/bin/activate
pipを用いてTensorFlowをインストールする。
TensorFlowをCPUのみで動作させる場合と、GPUを有効にする場合でURLが異なるので注意。
# Mac OS X, CPU only, Python 2.7:
(tensorflow)$ export TF_BINARY_URL=https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-0.10.0-py2-none-any.whl

# Mac OS X, GPU enabled, Python 2.7:
(tensorflow)$ export TF_BINARY_URL=https://storage.googleapis.com/tensorflow/mac/gpu/tensorflow-0.10.0-py2-none-any.whl
インストールの実行
(tensorflow)$ pip install --upgrade $TF_BINARY_URL

成功すると次のメッセージが表示される。
Successfully installed funcsigs-1.0.2 mock-2.0.0 numpy-1.11.1 pbr-1.10.0 protobuf-3.0.0b2 six-1.10.0 tensorflow-0.10.0

以上でインストールは終了です。

TensorFlow環境からexitする場合は次のコマンドを実行する。
(tensorflow)$ deactivate

2016年5月22日日曜日

iOSでFirebase Analyticsを使う


(引用元:https://firebase.google.com)

セットアップ

公式Page
https://firebase.google.com/docs/ios/setup

CocoaPodを利用してインストールします。
podの初期設定を実行。
pod init
Podfile を開いて以下を追加します。
pod 'Firebase/Core'
podのインストールを実行します。
pod install

Analytics以外の機能を使用するには、次の公式サイトに記載されているpodを追加しましょう。
https://firebase.google.com/docs/ios/setup#available_pods


import UIKit
import Firebase

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // init Firebase
        FIRApp.configure()
        return true
    }
}


イベント送信方法

以下、宣言済みのイベントがあり、ヘッダーファイルに定義されているので参照。
イベント:FIReventNames.h
パラメータ:FIRParameterNames.h


FIRAnalytics.logEventWithName(kFIREventSelectContent, parameters: [
  kFIRParameterContentType:"cont",
  kFIRParameterItemID:"1"
  ])


ユーザープロパティ

アプリのユーザーをカテゴライズすることが可能です。
FIRAnalytics.setUserPropertyString(food, forName: "favorite_food")

AndroidでFirebase Analyticsを使う


(引用元:https://firebase.google.com)

セットアップ

セットアップ方法は、必ず最新の情報を参照ください。
公式Page
https://firebase.google.com/docs/android/setup

プロジェクトのbuild.gradleに次のclasspathを追加する。
buildscript {
  dependencies {
    classpath 'com.google.gms:google-services:3.0.0'
  }
}


appのbuild.gradleに次のpluginを追加する。

// Add to the bottom of the file
apply plugin: 'com.google.gms.google-services'

Firebase Analyticsを使用するには、次のdependenciesを追加する。
dependencies {
    compile 'com.google.firebase:firebase-analytics:9.0.0'
}

Analytics以外の機能を使用するには、次の公式サイトに記載されているdependenciesを追加しましょう。
https://firebase.google.com/docs/android/setup#available_libraries

イベントの送信

イベントの種類
以下、宣言済みのイベントがあります。
https://support.google.com/firebase/answer/6317485?hl=en&ref_topic=6317484
FirebaseAnalytics.Event
https://firebase.google.com/docs/reference/android/com/google/firebase/analytics/FirebaseAnalytics.Event

  • Automatically collected events
  • Events: All apps
  • Events: Retail/Ecommerce
  • Events: Jobs, Education, Local Deals, Real Estate
  • Events: Travel (Hotel/Air)
  • Events: Games
  • Automatically collected user properties



宣言済みのイベント以外にも、オリジナルのイベントの追加が可能ですが以下の制限があります。

  • イベントは500種類まで
  • イベント名はユニークな名前であること
  • イベント名のPrefixに"firebase_"を付けない(SHOULD)
  • イベント名は32文字の、アルファベットとunderscores( _ )のみ


パラメータ
各イベントに25個までのパラメータを付加することが可能です。定義済みのパラメータがあります。
FirebaseAnalytics.Param
https://firebase.google.com/docs/reference/android/com/google/firebase/analytics/FirebaseAnalytics.Param

イベント同様、オリジナルのパラメータの追加が可能です。
  • パラメータ名は24文字の、アルファベットとunderscores( _ )のみ
  • パラメータの値は36文字まで
  • Prefixに"firebase_"を付けない(SHOULD)


イベントの送信方法
Bundleにパラメータを詰めて、logEventにイベント名とBundleを渡すだけ送信可能です。

FirebaseAnalytics mFirebaseAnalytics = FirebaseAnalytics.getInstance(this);
Bundle bundle = new Bundle();
bundle.putString(FirebaseAnalytics.Param.ITEM_ID, id);
bundle.putString(FirebaseAnalytics.Param.ITEM_NAME, name);
bundle.putString(FirebaseAnalytics.Param.CONTENT_TYPE, "image");
mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.SELECT_CONTENT, bundle);


ユーザープロパティ

アプリのユーザーをカテゴライズすることが可能です。
FirebaseAnalytics.UserProperty
https://firebase.google.com/docs/reference/android/com/google/firebase/analytics/FirebaseAnalytics.UserProperty

アプリごとに25種類までユーザー属性を追加することができます。

  • ユーザープロパティ名は24文字まで、アルファベットとunderscores( _ )のみ
  • ユーザープロパティの値は36文字まで
  • Prefixに"firebase_"を付けない(SHOULD)


FirebaseAnalytics mFirebaseAnalytics = FirebaseAnalytics.getInstance(this);
mFirebaseAnalytics.setUserProperty("favorite_food", mFavoriteFood);

2016年4月28日木曜日

[RxSwift] SectionありのDataSourceを生成する

本家のサンプルにも記載されている通り、RxDataSourcesを使用する方法があります。
https://github.com/ReactiveX/RxSwift/tree/master/RxExample/RxDataSources

RxDataSources:
https://github.com/RxSwiftCommunity/RxDataSources

Podfileに記載するなりしてインストールしてください。

SectionありのdataSourceを生成する

RxTableViewSectionedReloadDataSourceを使用します。SectionModelでSectionに表示するelementを指定します。

let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String, String>>()
let items = Observable.just([
    SectionModel(model: "First section", items: [
            "aaaa",
            "bbbb",
        ]),
    SectionModel(model: "Second section", items: [
            "cccc",
            "dddd",
        ])
    ])


セルの生成

以下のように、indexPathとelementが引数に入っているのでコールバック内でセルを生成する。
dataSource.configureCell = { (_, tableView, indexPath, element) in
    let cell = tv.dequeueReusableCellWithIdentifier("Cell")!
    cell.textLabel?.text = "\(element) @ row \(indexPath.row)"
    return cell
}


データとTablewViewをBindする

items
    .bindTo(tableView.rx_itemsWithDataSource(dataSource))
    .addDisposableTo(disposeBag)


ヘッダーのカスタマイズ

UITableViewDelegateで処理する必要があります。
以下のように、ヘッダー用のViewとHightを返すモジュールを実装します。
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    let view = UITableViewHeaderFooterView()
    view.textLabel?.text = dataSource.sectionAtIndex(section).model ?? ""
    return view
}
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return 44
}

2016年4月16日土曜日

[RxSwift] UITextFieldの入力テキストと通知

ViewModel側にObserverを作成する。ObserverでText更新を受け取ってViewModelの処理を行う。


let disposeBag = DisposeBag()
var account:Variable = Variable("")

// 参照方法
account.value



UITextFieldのrx_textとViewModelのObserverをBindする。
Textが更新されると、ViewModel側のObserverまで届く。
let disposeBag = DisposeBag()
accountTextField.rx_text
  .bindTo(viewModel.accountSubject.asObserver())
  .addDisposableTo(disposeBag)


プログラム上でTextを更新する場合(ストリームに流す場合)、sendActionsForControlEventsでValueChangedイベントをUITextFieldに流し込む。
accountTextField.text = "test"
accountTextField.sendActionsForControlEvents(.ValueChanged)

2016年4月15日金曜日

[RxSwift] UITableViewでカスタムセルを使用する

UITableViewで表示するデータをVariableで宣言するし、データの更新はvalueにセットする。

    let items = Variable<[Item]>([])
    // 新しいデータで更新
    self.items.value = updateItems

UITableViewにデータをBindingするには、rx_itemsWithCellIdentifierをコールする。
カスタムセルを使用する場合は、第二引数のcellTypeにセットが必要です。

    viewModel?.items
        .asDriver()
        .drive(tableView.rx_itemsWithCellIdentifier("CustomTableViewCell",cellType: CustomTableViewCell.self)) { row, item, cell in
            cell.item = item
            cell.tag = row
        }
        .addDisposableTo(disposeBag)


セルの選択はrx_itemSelectedを使用します。
tableView.rx_itemSelected
    .subscribeNext { [unowned self](indexPath) in
        self.tableView.deselectRowAtIndexPath(indexPath, animated: true)
        // get cell
        let cell: CustomTableViewCell = self.tableView.cellForRowAtIndexPath(indexPath)! as! CustomTableViewCell        
    }
    .addDisposableTo(disposeBag)

EditingStyleのセット

rx_itemsWithCellIdentifierを使用すると、DefaultでEditting可能になってしまいます。
不要の場合はUITableViewCellEditingStyle.Noneをセットしましょう。

func tableView(tableView: UITableView, editingStyleForRowAtIndexPath: NSIndexPath) -> UITableViewCellEditingStyle {
    return UITableViewCellEditingStyle.None
}

2016年4月12日火曜日

[Swift]循環参照(相互参照)によるメモリリーク

MVVM移行時にクラス分けを行った結果、ViewControllerのメモリリークが発生しました。deinitが呼ばれず期待動作となりませんでした。


循環参照(相互参照)によるメモリリーク

以下のような状態になると、メモリリークが発生します。
何も考えずにViewModelのDelegateを追加、DelegateにViewControllerをセットでメモリリーク発生


解決策

弱参照になるようにDelegate用プロトコルをclass継承し、weakで保持できるように対応。


protocolにclass継承追加
 public protocol ViewModelDelegate : class{
 }


weak追加
 weak var delegate: ViewModelDelegate?

2016年4月3日日曜日

[Swift]UIButtonでPressed状態の装飾を行う

AndroidならSelectorのxmlファイルを作成すればボタンの状態に合わせた装飾できます。
Swiftではボタンの文字などは次のfuncで設定可能です。

func setTitle(_ title: String?,forState state: UIControlState)
func setTitleColor(_ color: UIColor?,forState state: UIControlState)
func setTitleShadowColor(_ color: UIColor?,forState state: UIControlState)
func setBackgroundImage(_ image: UIImage?,forState state: UIControlState)

layerなどで装飾をする場合は、次のようにhighlightedのdidSetで変更可能です。

@IBDesignable class MyUIButton: UIButton {
    
    @IBInspectable var borderColor :  UIColor = UIColor.blackColor()
    @IBInspectable var borderHighLightedColor :  UIColor = UIColor.clearColor()
    
    override internal func awakeFromNib() {
        super.awakeFromNib()
    }
    
    override var highlighted: Bool{
        didSet{
            if (highlighted) {
                self.layer.borderColor = borderHighLightedColor.CGColor
            } else {
                self.layer.borderColor = borderColor.CGColor
            }
        }
    }
    // Attributes Inspectorで設定した値を反映
    override func drawRect(rect: CGRect) {
        self.layer.borderColor = borderColor.CGColor
    }

}

2016年3月27日日曜日

[Swift] Keyboardの表示に合わせてScrollViewの高さを変更する

Androidの場合、Keyboardが表示されると自動的にアプリケーションWIndowがリサイズされてKeyboardに被らないように表示されます。
iOSでは自前で実装する必要があります。

以下は、ScrollVIewを利用したサンプルです。
Keyboardが表示されたタイミングで、ScrollViewのcontentInsetとscrollIndicatorInsetsを変更します。

class AdjustScrollViewControll : NSObject{
    var scrollView : UIScrollView?
    
    init(scrollView : UIScrollView){
        self.scrollView = scrollView
    }
    
    func addObservers(){
        let notificationCenter = NSNotificationCenter.defaultCenter()
        notificationCenter.addObserver(self, selector: #selector(self.willShowNotification(_:)), name: UIKeyboardWillShowNotification, object: nil)
        notificationCenter.addObserver(self, selector: #selector(self.willHideNotification(_:)), name: UIKeyboardWillHideNotification, object: nil)
    }
    
    func removeObserviers(){
        NSNotificationCenter.defaultCenter().removeObserver(self)
    }
    
    func willShowNotification(notification: NSNotification) {
        
        let info = notification.userInfo
        let infoNSValue = info![UIKeyboardFrameEndUserInfoKey] as! NSValue
        let kbSize = infoNSValue.CGRectValue().size
        let contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height + 8, 0.0)
        scrollView!.contentInset = contentInsets
        scrollView!.scrollIndicatorInsets = contentInsets
    }
    
    func willHideNotification(notification: NSNotification) {
        let contentInsets = UIEdgeInsetsMake(0.0, 0.0, 0.0, 0.0)
        scrollView!.contentInset = contentInsets
        scrollView!.scrollIndicatorInsets = contentInsets
    }
}


ViewControllerからは次のようにコールします。
class HiddingKeyboardViewController: UIViewController,UITextFieldDelegate {

    @IBOutlet weak var scrollView: UIScrollView!
    
    var adjustTextFieldControll : AdjustScrollViewControll?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        adjustTextFieldControll = AdjustScrollViewControll(scrollView: scrollView)
    }

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        
        adjustTextFieldControll!.addObservers()
    }
    
    override func viewWillDisappear(animated: Bool) {
        super.viewWillDisappear(animated)
        adjustTextFieldControll!.removeObserviers()
    }
    
}

2016年3月26日土曜日

[Swift]アスペクト比を維持しつつUIImageViewのサイズを変更する

TableViewCellの実装で、次のような画面幅に合わせた画像を表示する際に困ったのでメモ。



Androidでは次のように指定することで、高さが自動で確定することができました。

<ImageView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:adjustViewBounds="true"/>

が、iOSでは簡単にはできず、カスタムViewを作ることで解決しました。
UIImageViewのimageがセットされる際に、アスペクト比を計算してConstraintを確定しているだけです。

class AspectAdjuestUIImageView: UIImageView {
    
    internal var aspectConstraint : NSLayoutConstraint? {
        didSet {
            if oldValue != nil {
                self.removeConstraint(oldValue!)
            }
            if aspectConstraint != nil {
                self.addConstraint(aspectConstraint!)
            }
        }
    }
    
    override var image: UIImage?{
        willSet{
            let aspect = newValue!.size.width / newValue!.size.height
            
            aspectConstraint = NSLayoutConstraint(item: self, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.Height, multiplier: aspect, constant: 0.0)
        }
    }
}

2016年3月23日水曜日

SwiftなCocoa Touch FrameworkでCommonCryptoを使う for iOS9.3

SwiftでCommonCryptoを使用する際、libcommonCryptoライブラリのリンクエラーが出たので対応方法を。

手順は次のようになります。

  1. CommonCryptoというディレクトリを作成
  2. module.mapというファイルを作成
  3. プロジェクト設定のBuild Settings -> Swift Compiler - Search Paths -> Import Pathsに、上記のCommonCryptoディレクトリを指定



次はサンプルです。 module.mapにSDK内のヘッダファイルのPathを記述します。

module CommonCrypto [system] {
    header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/CommonCrypto/CommonCrypto.h"
-    link "CommonCrypto"
    export *
}

link "CommonCrypto"を削除します。

2016年3月19日土曜日

[Swift]TableViewのSection HeaderをカスタムViewに置き換える

TableViewのSection HeaderをカスタムViewに置き換える方法です。



UITableViewHeaderFooterViewのサブクラスを作成

UITableViewHeaderFooterViewを継承したクラスを作成します。

import UIKit

class CustomTableViewHeaderFooterView: UITableViewHeaderFooterView {

    @IBOutlet weak var headerView: UIView!
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }
}


Nibファイルを作成

Header用のViewを作成します。



TableViewにセット

Cellと同様にtableView.registerNibでセットしておきます。tableView:viewForHeaderInSectionでロードして戻り値とします。
注意点としては、ViewのサイズがAutolayoutになっていないので、frame.sizeでサイズ指定します。

class ViewController: UITableViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let nib:UINib = UINib(nibName: "CustomTableViewHeaderFooterView", bundle: nil)
        tableView.registerNib(nib, forHeaderFooterViewReuseIdentifier: "CustomTableViewHeaderFooterView")
    }
    
    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }
    
    // header height
    override func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 44
    }
    
    
    // header view
    override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let header :CustomTableViewHeaderFooterView = self.tableView.dequeueReusableHeaderFooterViewWithIdentifier("CustomTableViewHeaderFooterView") as! CustomTableViewHeaderFooterView
        header.headerView.frame.size = CGRectMake(0, 0, tableView.frame.size.width, 44).size
        
        return header
    }
    
}

[Swift]iOSでGoogle Places API for iOSを使う

Google Places APIをiOSで使う方法です。
公式サイトはこちらです。

Google Places API for iOS
https://developers.google.com/places/ios-api/?hl=ja


SDKの追加

SDKはCocoaPodで公開されています。Podfileに次の項目を追加します。

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.1'
pod 'GoogleMaps'
Podfileに追加の上、installを実行するだけで完了です。

pod install


API Keyの取得

Google Developer Consoleにて、APIの設定が必要です。次はAPI Keyを取得するまでの手順です。

  • プロジェクトを作成
  • Google Places API for iOS と Google Maps SDK for iOSを有効にする
  • 認証情報からiOSキーを作成(BundleIDの登録が必要です)
  • API Keyを取得する



API Keyのセット

AppDelegate.swiftで、GMSServicesクラスにAPI Keyのセットします。
import GoogleMapsが必要です。

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        
        
        GMSServices.provideAPIKey("YOUR_API_KEY")
        return true
    }


Place Autocompleteの使い方

ViewControllerからGMSAutocompleteViewControllerを起動します。
オートコンプレートによるPlaceの選択結果は、GMSAutocompleteViewControllerDelegateで受け取ることができます。

extension ViewController: GMSAutocompleteViewControllerDelegate {
    func startGooglePlacesAutocomplete(){
        let autocompleteController = GMSAutocompleteViewController()
        autocompleteController.delegate = self
        self.presentViewController(autocompleteController, animated: true, completion: nil)
    }
    
    // Handle the user's selection.
    func viewController(viewController: GMSAutocompleteViewController, didAutocompleteWithPlace place: GMSPlace) {
        print("Place name: ", place.name)
        print("Place address: ", place.formattedAddress)
        print("Place attributions: ", place.attributions)
        self.dismissViewControllerAnimated(true, completion: nil)
    }
    
    func viewController(viewController: GMSAutocompleteViewController, didFailAutocompleteWithError error: NSError) {
        // TODO: handle the error.
        print("Error: ", error.description)
    }
    
    // User canceled the operation.
    func wasCancelled(viewController: GMSAutocompleteViewController) {
        self.dismissViewControllerAnimated(true, completion: nil)
    }
    
    // Turn the network activity indicator on and off again.
    func didRequestAutocompletePredictions(viewController: GMSAutocompleteViewController) {
        UIApplication.sharedApplication().networkActivityIndicatorVisible = true
    }
    
    func didUpdateAutocompletePredictions(viewController: GMSAutocompleteViewController) {
        UIApplication.sharedApplication().networkActivityIndicatorVisible = false
    }
    
}


GMSAutocompleteViewControllerの起動が成功すると、次のような画面が表示されます。Placesの確定時にdidAutocompleteWithPlaceがコールされるので、引数:placeで値を受け取ります。

2016年3月5日土曜日

BottomSheetの実装方法 via Android Design Support Library 23.2

Android Design Support Library 23.2でBottomSheetが追加されました。
GoogleMapなどに用いられている画面が作成可能になりました。
実装はCoordinatorLayoutの子Viewに対して、Behaviorを指定するだけとシンプルです。

AOSPのソースコード : BottomSheetBehavior.java

Layoutのサンプル

BottomSheetはBehaviorを指定することで実装します。
CoordinatorLayoutの子ViewとしてLayoutを追加して、BottomSheet化したいLayoutに対してapp:layout_behavior="@string/bottom_sheet_behavior"を指定します。

<android.support.design.widget.CoordinatorLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

    <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            app:layout_behavior="@string/bottom_sheet_behavior"
            app:behavior_peekHeight="240dp"
            app:behavior_hideable="false"
            android:background="@android:color/white">
        <TextView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:text="BottomSheet Sample"/>
    </LinearLayout>
</android.support.design.widget.CoordinatorLayout>

次のパラメータの指定が可能です。

  • app:behavior_peekHeight
    • BottomSheetの最小表示サイズ
  • app:behavior_hideable
    • 下スクロールで完全に非表示にするかどうか。非表示後に上スクロールで表示可能


2016年3月2日水曜日

[Swift]NavigationControllerのNavigationBarの表示 / 非表示方法

NavigationControllerを使っている際、画面上部のBarを表示したくないことがあります。
viewDidLoad()で表示/非表示するのではなく、viewWillAppear()とviewWillDisappear()で表示切り替えを行い、
画面遷移後のControllerに処理させないようにしました。

    override func viewWillAppear(animated: Bool) {
        self.navigationController?.navigationBarHidden = true
    }
    
    override func viewWillDisappear(animated: Bool) {
        self.navigationController?.navigationBarHidden = false
    }

2016年2月13日土曜日

Android M Fingerprint APIについて調べてみた

関西モバイル研究会で発表した内容です。

Android M FingerPrint(public)
http://www.slideshare.net/baroqueworksdev/android-m-finger-printpublic

簡易クラス図

アプリからHAL層までの簡易クラス図です。



アプリケーションから使用する場合

  • Context#getSystemService()でFragmentprintManagerにアクセス 
  • コールバックで結果を受け取る

FingerprintManagerクラス

  • アプリケーションから要求を受け取る
  • Android Frameworksのサービス群にある、FingerprintServiceに連携
  • 認証結果をFingerprintServiceから受け取り、アプリケーションに通知する

FingerprintServiceクラス

  • SystemServiceクラスを継承
  • Android Systemとして指紋認証機能を実行
  • Native(JNI->HAL)につなぐ
  • 認証要求時にFingerprintDeamonクラスをとおして、Native層に通知 アプリケーションの突然死の際、unbindする(IBinder.DeathRecipient)

JNI / HAL層

  • HAL層(ライブラリ)をとおして、Kernelに通知、ハードウェアの制御を行う
  • 認証結果をKeystoreServiceに通知