配列の要素をオプショナルバインディング

はじめに

コードレビューで、ちょっと不思議なコードがあったのでメモ。

結論

配列の添字アクセスをオプショナルバインディングしてもビルドエラーにならないけど、わかりにくいのでやめたほうがいい。

コード

var hoge:[Int] = []

// hogeを更新する処理など

if let n: Int = hoge[0] { //ランタイムエラー
    print(n)
}
else {
    print("nothing")
}

一見すると良さそうですが、配列が空っぽなのでコメントした行で fatal error: Index out of range になります。

当たり前といえば当たり前ですね。

改善案

配列でこういった判定を行うなら、空配列の判定したほうがわかりやすい気がします。

if hoge.isEmpty {
    print("nothing")
}
else {
    print(hoge[0])
}

そもそも、配列アクセスでnilが返る可能性を想定したコードになっていますが、hogeInt型の配列であって、Optional<Int>型の配列ではないので、オプショナルバインディングは適切ではないです。

なんでビルドエラーにならないのかは謎です。

どうしてもオプショナルバインディングしたい場合

if let n: Int = hoge.first {
    print(n)
}
else {
    print("nothing")
}

今回の場合は、配列の最初の要素にアクセスしたいので、Array クラスの first プロパティでOK。

first はオプショナル型なので問題なし。

似たようなプロパティとして最後の要素を参照する last もあります。

どちらも正確には Array クラスに適用している CollectionType プロトコルの特徴ですが。

PHPで画像やcssの404エラーでセッション切れになる現象

はじめに

FuelPHPを使っていて、セッションが切れることがあり調査していました。

ググるとドキュメントルートにファビコンが存在していないと発生するなどの記事がヒットするのですが、ファビコンだけでなく読み込んでいる画像やcssがなくても同じ事象が発生することがわかったのでまとめておきます。

結局FuelPHPだけの話ではなかったです。

原因

FuelPHPに限らず、PHPのWebアプリケーションフレームワークだと、たいてい.htaccessにこんな感じの設定があります。

リクエストされたパスがファイルやディレクトリじゃなかったら、フロントコントローラーであるindex.phpに処理を投げるというものです。

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /index.php$1 [L]

通常、画像やcssは最初の条件に該当するのでindex.phpで処理しないのですが、ファイルが存在しない場合、どちらの条件にも該当せずindex.phpで処理することになります。

コントローラーには画像やcssファイル名のアクションはないので、最終的に404に流れます。

index.php内(というか、その先のコントローラーなど)で、FuelPHPで言うフラッシュセッションのような次のリクエスト時に消えるセッションを使用していると、404リクエストで消えてしまうためセッション切れが発生します。

生存期間の長い通常のセッション使っても解決するのですが、フォームデータの削除し忘れとか嫌ですし。

対策

コントローラのアクション以外をindex.phpで処理しなければいいので、.htaccessの設定をこんな感じにすれば良いです。

特定の拡張子(この例ではcss/jpeg/png)の場合、index.phpで処理しないようにしています。

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !^.*\.(css|jp?g|png)$
RewriteRule ^(.*)$ /index.php$1 [L]

Sketchで透過オブジェクトを含むグループごとスライスする

はじめに

最近、PhotoShopの代わりにSketchを使うことも増えてきました。

SketchはMake Exportableで@2xや@3xもまとめて簡単に切り出せていいなーとか思ってたら、よくあるアイコンの下に固定サイズの透過オブジェクトがあるパターンで、グループごと切り出しても透過オブジェクトのサイズに切り出せずアイコン自体のサイズになってしまって困りました。

プラグインとかいれないで解決できたのでメモ。

確認バージョン

Version 3.6.1 (26313)

解決策

グループ化する

ヘルプアイコンと24x24の矩形をグループ化。 f:id:y_sumida:20160615155147p:plain

アイコンと透過レイヤーを選択

f:id:y_sumida:20160615155146p:plain

スライスツールを使ってオブジェクトを選択

スライスツールはここ。

f:id:y_sumida:20160615152532p:plain

外側を選ばないと、アイコンだけ切り出すことになるので注意。

f:id:y_sumida:20160615155145p:plain

アイコンだけ選んだ場合

f:id:y_sumida:20160615155144p:plain

選択すると切り出した結果が右側に表示されるが、背景も含んでしまっている。

f:id:y_sumida:20160615155143p:plain

Export Group Contents Onlyにチェック

グループ以外のオブジェクトが除外されるので透過になる。

f:id:y_sumida:20160615155142p:plain

あとは必要に応じて@2や@3を追加してExportShapeでOK。

iOSで表示要素を動的に消して親ビューの高さを変える

はじめに

ログイン前後で表示要素の数を動的にし、ビューの高さを変えるという仕様があり、実現方法を調べました。

意外とめんどくさかった。もっと効率の良いやり方もありそうです。

バージョン

XCode Version 7.3.1 (7D1014)

サンプル

ラベル2つとボタン1つを持つフッターがあり、状態によって2つ目のラベルを非表示にして、その分ボタンを上に詰めるという仕様で考えます。

やりかた

制約

添付のような状態にします。

ポイントはボタンのconstraintsにLabel2を使わないこと。

ここでは高さと左右下のマージンだけ設定しています。

f:id:y_sumida:20160615152027p:plain

コード

調べるまで知らなかったのですがconstraintsもIBOutletでViewControllerに接続できます。

フッターの高さとLabel2の上マージンを接続して、viewDidLoadでLabel2を非表示にするため高さを0に、空いた隙間を詰めるためフッターの高さを調整しています。

確認用にボタンタップで表示するようにしています。

もちろん、他のタイミングでもできるのでやりたいことに合わせて読み替えましょう。

ちなみに、Label2自体をIBOutletで接続してhiddentrueで非表示にしても、htmlと違って高さを維持するのでうまくいかないです。

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var footerHeight: NSLayoutConstraint!
    @IBOutlet weak var label2TopMargin: NSLayoutConstraint!

    override func viewDidLoad() {
        super.viewDidLoad()
        self.label2TopMargin.constant = 0
        self.footerHeight.constant = 120
    }

    @IBAction func tapButton(sender: AnyObject) {
        self.label2TopMargin.constant = 4
        self.footerHeight.constant = 150
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

実行結果

初期ロード時

Label2が非表示でLabel1とボタンの間が詰まっています。

f:id:y_sumida:20160615152125p:plain

ボタンタップ時

フッターの高さが高くなり、Label2を表示しています。

f:id:y_sumida:20160615152135p:plain

PHPのDateTimeクラスを使って日時の差分を取る時に注意すること

はじめに

とても今さら感のあるネタですが自分用にメモ。

プログラムで日付や時間を比較して何かを処理したい場合は多いはず。

PHPだとDateTimeクラスというのがあって、比較演算子==><などが使えます。

大小比較だけでなく、日時としての差分を取るためのDateTime::diff()メソッドも用意されていて、2つのDateTimeクラスの差分を表すDateIntervalクラスを返します。

DateInterval::format()メソッドを使って差分を確認するのですが、ちょっと癖があると思うので注意が必要です。

使い方は、下記の実験を見て貰えばわかるはず。

実験

翌日との比較

コード

<?php
$datetime1 = new DateTime('2015-11-20');
$datetime2 = new DateTime('2015-11-21');
$interval = $datetime1->diff($datetime2);

// %Rは符号を出力するオプション
echo $interval->format('%R%Y') . "\n"; // 年
echo $interval->format('%R%M') . "\n"; // 月
echo $interval->format('%R%D') . "\n"; // 日
echo $interval->format('%R%H') . "\n"; // 時
echo $interval->format('%R%I') . "\n"; // 分

結果

+00 // 年
+00 // 月
+01 // 日
+00 // 時 ?
+00 // 分 ??

翌月の同日との比較

コード

<?php
$datetime1 = new DateTime('2015-11-20 ');
$datetime2 = new DateTime('2015-12-20');
$interval = $datetime1->diff($datetime2);

echo $interval->format('%R%Y') . "\n"; // 年
echo $interval->format('%R%M') . "\n"; // 月
echo $interval->format('%R%D') . "\n"; // 日
echo $interval->format('%R%H') . "\n"; // 時
echo $interval->format('%R%I') . "\n"; // 分

結果

+00 // 年
+01 // 月
+00 // 日 ?
+00 // 時 ??
+00 // 分 ???

翌月の同日との比較その2

コード

<?php
$datetime1 = new DateTime('2015-11-20 ');
$datetime2 = new DateTime('2015-12-20');
$interval = $datetime1->diff($datetime2);

echo $interval->format('%R%Y') . "\n"; // 年
echo $interval->format('%R%M') . "\n"; // 月
echo $interval->format('%R%a') . "\n"; // 日 Dではなくaオプション(総日数)
echo $interval->format('%R%H') . "\n"; // 時
echo $interval->format('%R%I') . "\n"; // 分

結果

+00 // 年
+01 // 月
+30 // 日 やったぜ!
+00 // 時
+00 // 分

解説

要するに、DateTime::diff()で算出するのは、2つの日付の年月日時分秒のそれぞれの差分ということです。

上記の実験では%Hで時間の差を表示すると、どちらも時間が省略されているので0時と解釈されて、0時と0時の比較なので差は0時間です。

今日の0時と翌日の0時で比較した場合、差として24時間を期待したので、ちょっとはまりました。

唯一、日数のみ%aオプションというのが用意されていて総日数が取得できます。

全部にそういうオプション欲しい。

参考

http://php.net/manual/ja/datetime.diff.php

FuelPHPでデフォルトのタイムゾーンと異なるタイムゾーンの日付を取得したい

はじめに

通常、FuelPHPにおけるタイムゾーンは`fuel/app/config/config.php`の`default_timezone`で設定します。

でも、ある日付を別のタイムゾーン、例えばUTC協定世界時)に変換したい時ってありますよね。

WebAPIの日付指定がUTC限定だったり。

そんな時は、Fuelには便利なクラスが用意されてるので、それを使いましょう。

やりかた

Dateクラスを使います。

コード

タイムゾーンが`Asia/Tokyo`の前提です。

`Date::display_timezonel()`でutcを設定した上で、`Date::format()`の第二引数に`true`を指定すると、`Date::display_timezonel()`の日付を取得できます。

簡単ですね。

$date = \Date::create_from_string('2015/11/11/23:00', "%Y/%m/%d%H:%M");
$date->display_timezone('utc');
echo $date->format('%Y/%m/%dT%H:%M', true);
出力
2015/11/11/14:00

Vagrantのベースボックス更新をするスクリプト

はじめに

最近、仕事でVagrantを使って仮想環境を構築して使うことが多いです。

たいていの場合、最初にここからベースボックスを取得して、パッチ更新、各種ミドルウェアのインストールなどをしてメンバーに配布します。

開発を進める中で、ボックスの内容が大きく変わったときや、プロビジョニングに時間がかかるようになってきたら、vagrant packageでボックスを更新して再配布してます。

再配布時にはvagrant destroy -> vagrant box remove -> vagrant box addという手順でやっていたのですが、オプションを眺めていたらvagrant box add --clean --forcevagrant box removevagrant box add同等ということを知りました。

わかったついでに、黒い画面が苦手な人にも配布したりするので、できればコマンド一発にしたいと思ってシェルスクリプトにしてみました。

ファイル名固定だったり、destroy前に確認してなかったり、改善余地はありますが使えるはず。

json使う方法も検討したけど、バージョニングするほどでもないなってことで見送りました。

使い方

配布したVagrantfileとpackage.boxと同じディレクトリに置いて実行。

私のチームではVagrantfileと一緒にコミットしてます。

コード

#!/bin/sh
if [ ! -e ./Vagrantfile -o ! -e ./package.box ]; then
    echo "not found ./Vagrantfile or ./package.box"
    exit
fi

BOX=`sed -n 's/^.*config.vm.box.*=.*"\(.*\)"/\1/p' ./Vagrantfile`

if [ -z ${BOX} ]; then
    echo "not found config.vm.box parameter in ./Vagrantfile"
    exit
fi

echo "##### ${BOX} setup start\n"

echo "##### vagrant destroy\n"
vagrant destroy -f

echo "##### vagrant box add ${BOX}\n"
vagrant box add -c -f ${BOX} ./package.box

echo "##### ${BOX} setup end\n"