読者です 読者をやめる 読者になる 読者になる

UIViewからUITableViewのカスタムセルを作るとボタンタップイベントが取れない

iOS AutoLayout

現象

UITableVIewのカスタムセルにボタンを置いて、タップ用の処理を書いたがタップイベントを検知できない。

原因

ボタンの上のレイヤーに、UITableViewCellContentView というビューが描画されており、このビューに邪魔されてタップイベントが検知できていなかったため。

f:id:y_sumida:20170301094905p:plain

根本原因

カスタムセルのxibファイルを、UITableViewCell ではなく、 UIView を使って作成していたため。

UITableViewCell で作ったxibは、UITableViewCellContentView の上に自分の配置したパーツが描画されています。

f:id:y_sumida:20170301094946p:plain

ちなみに、xibファイルの新規作成すると、デフォルトで UIView が置いてあるというXCodeのおせっかい機能のがほんとの原因かもしれません。

f:id:y_sumida:20170301095134p:plain

対策

カスタムセルを作る際には、xibファイルのデフォルトで置いてある UIView を消して、UITableViewCell を置くと良いです。

f:id:y_sumida:20170301095039p:plain

または、新規ファイルを作成する際に、クラスファイルから作成して、 Cocoa Touch Class を選択して、次の画面で Also create XIB file にチェックを入れると、勝手に UITableViewCell を配置したxibファイルを生成してくれます。

f:id:y_sumida:20170301095245p:plain

f:id:y_sumida:20170301095341p:plain

AutoLayoutに慣れてる人には当たり前なのかもしれないですが、ちょっとわかりにくいなーと思いました。

2016年の振り返り

その他

今年もあんまりブログ書けなかったけど、振り返りはしておく。

お仕事全般

こまごまPHPもやったけど、ほぼSwiftでiOSアプリ開発ばっかりだった。

チームでのアプリ開発を経験できたり、RxSwift触ってみたり楽しかった。

プチSCMBCとかを開催して、ちょっとずつプルリクエスト方式での開発ができるようになってきた。

Qiita:Team

去年に引き続き、積極的に投稿するようにしていた。

だんだん書いてくれる人が増えてきて嬉しい。

少しは役に立っているのかも。

社内勉強会

Bitbucketを使うことが増えてきたので、プチSCMBCみたいなことをした。 コンフリクト祭りが楽しかった。

全職種向けにバージョン管理についての勉強会を開催して日付zipがなぜダメなのかを伝えた。

社外勉強会など

ほぼ参加できなかったけど、まあ、これは仕方ない。

プライベート

第二子である次男が生まれて家の中がさらに賑やかになった。

長男が保育園に通うようになって、より時間を効率的に使う必要が出てきた。

早めに出社して勉強してたのが、子どもを寝かしつけてからの時間に変わった。

たまに一緒に寝落ちしちゃうのどうにかしたい。

来年の目標

  • Qiita:Teamをディレクターさん、デザイナーさんも使うようになってきたのでそっち向けの記事も書く
  • Swift環境での自動テストを導入する
  • iOSアプリ開発に偏った1年だったので、Web開発もうちょっとがんばる
  • 何かしらアウトプットする

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

Swift

はじめに

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

結論

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

コード

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エラーでセッション切れになる現象

PHP

はじめに

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で透過オブジェクトを含むグループごとスライスする

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で表示要素を動的に消して親ビューの高さを変える

iOS XCode

はじめに

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

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

バージョン

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

はじめに

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

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

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