wootan's diary

iOSアプリ開発を中心としたエンジニアブログ

Xcodeプラグイン紹介

iOS10は評判が良さそうですね!
未だに触れていないですが時間をみつけて触ってみたいと思います。

本エントリーではXcodeプラグインについて紹介したいと思います。

Alcatraz

まず、Xcodeプラグインを管理するパッケージマネージャを紹介します。
プラグインのインストールは全てこれで行うのがおすすめです。
alcatraz.io

インストール

ターミナルからコマンドでインストールすることができます。

curl -fsSL https://raw.githubusercontent.com/supermarin/Alcatraz/deploy/Scripts/install.sh | sh

使い方

Xcode -> Window -> PackageMangerから起動することができます。

起動するとこのような画面が表示されます。

画面上部の検索フォームか検索することができます。
画面左部に表示される INSTALL ボタンから
簡単にプラグインを導入することができます。

プラグイン紹介

AutoHighlightSymbol

github.com

カーソルがあたっているシンボルにハイライトがつきます。

Xcode -> Editor -> Auto Highlight Symbol を
チェックすると有効になります。


Auto-Importer

github.com

インポート文を挿入するためのプラグインです。
cmd + ctrl + h で下記のようなポップアップが表示されます。

選択したもののimport文が自動的に挿入されます。


Backlight

github.com

カーソルがどの行にあたっているかわかりやすくするプラグインです。
インストールするとこのように表示されます。

よくカーソルを見失うので助かっています。


BlockJump

github.com

ブロックジャンプ用のプラグインです。
ctrl + [ で前のブロックに
ctrl + ] で次のブロックにジャンプできます。


ClangFormat

github.com

コード整形プラグインです。
Xcode -> Edit -> Clang Format -> Click Format on save
をチェックすると保存時にFormatされます。

保存時だけでなく任意のタイミングでフォーマットをかけたい場合は
ショートカットキーを設定することができます。

システム環境設定 -> キーボード -> ショートカット-> アプリケーション -> +ボタンをクリック

アプリケーション : Xcode.app
メニュータイトル : Format File in Focus
キーボードショートカット : ctrl + i

この設定で ctrl + i で任意のタイミングでフォーマットすることができます。


Formatはデフォルトで以下のようなものがあります。

スタイルはカスタマイズすることもできます。詳しくはGitHubを参照してください。


CocoaPods

github.com

CocoaPodsをXcode上で操作できるようにするプラグインです。
Xcode -> Product -> CocoaPodsから各メニューを選択することできます


ColorSenseRainbow

github.com

UIColorの指定を簡単にするプラグインです。

UIColorのプレビューが表示されるようになります。
さらにプレビュー部分をクリックすると標準の色選択ポップアップが表示されます。


DerivedData Exterminator

github.com

DerivedDataを1クリックで削除できるようにするプラグインです。

Xcode -> View Derived Data Exterminator in Toolbarを
チェックするとこのように表示されます。

真ん中のアイコンをクリックするとDerivedDataが削除されます。


GitDiff

github.com

コードの差分を表示するプラグインです。

blameやlogも確認することができます。


ImportSorter

github.com

インポート文をソートするプラグインです。
ctrl + s でインポート文がソートされます。


KSImageNamed

github.com

プロジェクトに存在するイメージファイルがサジェストされるようになるプラグインです。

このようにプレビューも表示されます。


MCLog

github.com

ログをフィルターすることができるようになるプラグインです。

ここに文字列を入力するとフィルタされます。


VVDocumenter-Xcode

github.com

Doc自動生成プラグインです。
/// と入力するとDocを自動的に生成してくれます。


XToDo

github.com

ctrl + t で //TODO: //FIXME: などを一覧で
表示できるようになるプラグインです。


以上になります。
紹介したもの以外でおすすめのものがあれば教えてください!
他に良い物をみつけたら追記していきたいと思います。

relux Swift もくもく会 #1 を開催します!

WWDCのせいで寝不足です...
突然ですが弊社オフィスでSwiftもくもく会を開催します。

  • Swiftを使って何かをつくりたいエンジニア
  • Swiftをこれからはじめたいエンジニア
  • iOSアプリ開発者と交流したいエンジニア
  • reluxやアプリ開発に興味のあるエンジニア

もくもくしながら情報共有や交流を行える場になります。
下記から参加申し込みをお待ちしております!

relux-swift-mokumoku.connpass.com

アプリUIUX事例研究 # 1 TALKIE

もうすぐWWDC2016ですね!
発表内容が今から楽しみです。

アプリUIUX事例研究

"イケてるアプリ"の"イケてるUIUX"を研究する。
話題のアプリや気になったアプリを触って
良いと思ったものを紹介していきたいと思います。
第1回目は「TALKIE」を紹介します。

ウォークスルー


自然なアニメーションで表示内容が切り替ります。


担当者の方とチャットでやりとりできるようです。


最後に「かんたん登録ボタン」と「試してみるボタン」が表示されます。

会員登録画面

会員登録画面は通常のフォームではなくチャット風のUIになっています。
LINEやFacebookなどのChatbotも話題になっているので
今後このようなUIが増えていくかもしれませんね。


入力欄はキーボードの上に表示されるので迷いません。


質問内容によってはPickerViewが表示されます。


誕生日も同じようにPickerViewが表示されます。


駅名の入力はモーダルビューでサジェストが表示されます。


都道府県の選択もわかりやすいですね。
文字の入力はかなり面倒なのでこのような形の方が良いですね。


最後まで入力がおわると「次へ」のボタンが表示されます。

詳細画面

画面下部のタブはiOS標準アプリとおなじようなトーンなっています。


青い部分がタップ可能なテキストです。
こちらもiOS標準と同じなのでわかりやすいですが
タップすると赤くなるのが少し気になりました。

設定画面


メニューは色でカテゴライズされています。
使用頻度が低そうなものはグレーでまとめられています。


以上になります。
会員登録がチャット風のUIになっていて登録するだけでも楽しいですね。
入力方法もフォームの内容によって適切なものになっているので使い勝手も良さそうです。

このような感じで色々なアプリを紹介していきたいと思います。

talkieapp.jp

CocoaPods関連のメモ

ネットワークの問題でCocoaPodsのsetupが終る気配がないので
この記事を書いています...
本エントリではCocoaPodsの基本的な使い方などについて簡単にまとめたいと思います。
cocoapods.org

CocoaPodsとは

外部ライブラリの依存管理ツールです。
簡単な記述で外部ライブラリを導入することができます。
iOSアプリ開発ではおなじみのツールですね。

インストール

#gemを最新にする
sudo gem update --system

#インストール
sudo gem install cocoapods

初期設定

#セットアップ
pod setup

#プロジェクトのディレクトリまで移動
cd projectdir
#プロジェクトファイルを元に初期化
pod init

pod initを実行するとプロジェクトフォルダ内にPodfileが生成されます。
ライブラリの指定はこのPodfileに記述していきます。

ライブラリの指定

target 'sample' do
   pod 'hoge'
   pod 'fuga', '1.0'
end

hogeのように記載する場合はupdate時に最新版が適用されます。
fugaのように記載する場合は1.0が適用されます。
以下にバージョンの記載方法についてまとめます。
(この書き方がわからなくなることがあります...)

記載例 適用されるバージョン
'1.0' バージョン1.0
'>=1.0' バージョン1.0以上
'<1.0' 'バージョン1.0未満'
'~>0.2' バージョン0.2以上1.0未満

ライブラリのインストール

指定ライブラリのインストールを行います。
インストール時にPodfile.lockが作成されます。
Podfile.lockファイルが存在する場合は
Podfile.lockに記載されているバージョンで固定されます。

#インストール
pod install

ライブラリのアップデート

指定ライブラリのアップデートを行います。
Podfile.lockファイルが存在している場合でも
Podfileでバージョン指定されていないものはアップデートされます。

# アップデート
pod update

pod updateがうまくいかないとき

Xcodeを閉じて下記コマンドを実行するとうまくいく場合があります。

#リポジトリ削除
pod repo remove master

#セットアップ
pod setup

#インストール
pod install

pod update時にリポジトリの更新をスキップしたいとき

このようなオプションをつけるとスキップできます。
結構時間がかかることがあるので覚えておくと便利です。

pod update --no-repo-update

CocoaPods 1.0.0で気をつけること

以前のバージョンではtarget指定が必須ではなかったのですが1.0.0から必須になっています。ビルド時にエラーになる場合はプロジェクトのクリーンやDerivedDataの削除で動くようになると思います。

relux iOSアプリでディープリンク対応

relux iOSアプリでディープリンク対応を行いました。
ちょうどiOS9.3がリリースされたあとに着手しはじめ、
iOSの不具合でUniversalLinksが全く動かずかなりハマりました。
ブラウザがまともに動かず悩まされた人も多いのではないでしょうか?

今回はディープリンクを以下と定義し対応方法を紹介したいと思います。

Universal Linksとは?

iOS9から導入された仕組みです。
Universal Linksでは通常のURLから直接アプリを起動することができるようになります。

  • アプリがインストール済:アプリが開く
  • アプリが未インストール:Webページが開く

CustomURLSchemeではSafariを経由してアプリを起動しますが、
Universal Linksではアプリにシームレスに遷移するのでUXがかなり改善されます。
また、CustomURLSchemeとは違いschemeが衝突する心配もありません。

developer.apple.com

f:id:wootan1102:20160419213700g:plain

実際の画面はこのような動きになります。
Googleの検索結果からアプリを起動しています。

Facebook App Linksとは?

Facebookアプリから直接アプリを起動することができるようになります。
Universal LinksはiOS9以降のみ対応ですが、こちらはそれ以前のOSでも動作します。

  • アプリがインストール済:アプリが開く
  • アプリが未インストール:AppStoreが開く

App Links - 参考資料 - 開発者向けFacebook

f:id:wootan1102:20160419213746g:plain

実際の画面はこのような動きになります。
Facebookからアプリを起動しています。

対応するページ

reluxの旅館・ホテルの施設詳細ページのみ対応しました。
Facebook, Instagram, Twitter などのSNSでURLがシェアされた際に
アプリが起動されることを想定しています。
※2016年4月現在

サーバサイドの対応

apple-app-site-associationファイルを作成してルート直下に配置します。

Universal Links
apple-app-site-associationファイル

{
  "applinks": {
    "apps": [],
    "details": [
      {
        "appID": "4M2H9MG272.com.loco-partners.relux",
        "paths": [
            "/2????/",
            "/5????/"
        ]
      }
    ]
  }
}

appID: TeamID + BundleIndentifierの組み合わせです。
paths: 対応するパスを記載します。
施設詳細のURLは
https://rlx.jp/20000/ もしくは https://rlx.jp/50000/ のように2または5からはじまる5桁の数字と決まっています。
パスは一般的な正規表現には対応していないので "2????", "5????" としました。

ポイント
  • 拡張子".json"は不要
  • "*"は任意の部分文字列
  • "?"は任意の一文字
  • "NOT"をつけると除外


Facebook App Links

WebページにMETAタグを入れる必要があります。

<meta property="og:url" content="https://rlx.jp/27500" />
<meta property="og:image" content="https://s3-ap-northeast-1.amazonaws.com/relux/img/hotelpictures/RP27500_1046.jpg" />
<meta property="og:site_name" content="relux" />
<meta property="fb:app_id" content="482299358457316" />
<meta property="al:ios:url" content="rlx://hotels/27500/" />
<meta property="al:ios:app_store_id" content="843104033" />
<meta property="al:ios:app_name" content="relux" />

al:ios:url      CustomURLSchemeを指定
al:ios:app_store_id  AppleIDを指定
al:ios:app_name   アプリ名を指定

ポイント
  • FacebookDebuggerで警告がでているとうまく動作しません
  • METAタグが存在しない状態でキャッシュされると動作しません

https://developers.facebook.com/tools/debug/

iOSアプリ側の対応

Capabilitiesの設定
Associated DomainsのDomainsを設定します。
f:id:wootan1102:20160417221955p:plain
applinks:url という形で指定します。

Universal Links

- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler {
    if ([userActivity.activityType isEqual:NSUserActivityTypeBrowsingWeb]) {
        int hotelId = [self matchHotelId:userActivity.webpageURL pattern:UNIVERSAL_LINKS_PATTERN];
        if (hotelId) {
            [self pushHotelDetailViewController:hotelId];
        } else {
            [application openURL:userActivity.webpageURL];
            return NO;
        }
    }
    return YES;
}
ポイント
  • ActivityTypeはNSUserActivityTypeBrowsingWebになります
  • 対応しないURLの場合は openURL でブラウザを開いたほうが良いです

Facebook App Links

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
    if ([url.scheme isEqualToString:CUSTOM_URL_SCHEME]) {
        int hotelId = [self matchHotelId:url pattern:CUSTOM_URL_SCHEME_PATTERN];
        if (hotelId) {
            [self pushHotelDetailViewController:hotelId];
            return YES;
        }
    }
    return NO;
}
ポイント
  • CustomURLSchemeに関する実装がされていれば、追加で実装する必要はありません

これから

ディープリンクはUXを改善できるものですが
対応するコンテンツが正しく、素早く表示されることが前提にあります。
どのページを対応するか、表示速度に問題ないかなど慎重に検討をおこなう必要があります。
reluxでは将来的に検索画面やrelux selections, relux Magazineなんかも対応したいと考えています。

rlx.jp
rlx.jp

relux 開発合宿 in 箱根湯本

先日、relux開発合宿を箱根湯本で行いました。
オフィスを離れ、リラックス&集中できる環境で開発に取り組めました。
その時の様子を紹介したいと思います。

お世話になった施設

今回お世話になったのは 「ホテルおかだ」という和風リゾートホテルで
4/15(金)〜4/16(土)の1泊2日の合宿を行いました。
手配はreluxコンシェルジュデスクにお願いしました!

www.hotel-okada.co.jp
rlx.jp

開発合宿の目的

しっかり時間をとり、普段取り組めないことを行う
優先度が低く、あとまわしになってしまっている部分を改修することにしました。
アプリチームでは「ユーザ登録情報変更画面のマテリアルデザイン対応」を行いました。

開発合宿の様子

新宿駅へ集合(1日目 07:00)

早朝に移動して10時から開発する計画です。
特急券を予約したリーダーが寝坊しましたがギリギリ間に合いました。

f:id:wootan1102:20160417230754j:plain
ロマンスカーでの移動

f:id:wootan1102:20160417230822j:plain
座席の説明

f:id:wootan1102:20160417231951j:plain
イケメンAndroidエンジニア

f:id:wootan1102:20160417230837j:plain
移動中の車内でフライング開発?

到着・開発開始(1日目 10:00)

f:id:wootan1102:20160417231106j:plain
箱根湯本駅到着

f:id:wootan1102:20160417231114j:plain
ホテルおかだ到着

f:id:wootan1102:20160417233748j:plain
会議室は部屋の近くでした

f:id:wootan1102:20160417231138j:plain
会議室は壁がホワイトボードで便利

昼食・散歩(1日目 12:00)

昼食は暁庵でお蕎麦をいただきました。
www.hakoneyumoto.com

近くにコンビニはありませんが箱根ベーカリーがあります。
メロンパンがすごく美味しいです。
少し歩くと滝もあるのでリフレッシュには最適です。

f:id:wootan1102:20160421191231j:plain
暁庵

f:id:wootan1102:20160417234106j:plain
見事なコシです

f:id:wootan1102:20160417234130j:plain
天ぷらも美味しい

f:id:wootan1102:20160417234437j:plain
玉簾の瀧

f:id:wootan1102:20160421191909j:plain
アヒルちゃん

f:id:wootan1102:20160418013521j:plain
箱根ベーカリー

開発再開

19時の夕食までかなり集中して開発できました。
途中で不具合にハマったので気分転換に足湯へ
足湯のおかげか不具合も無事に解消しました。

f:id:wootan1102:20160418000359j:plain
気持ちいい

f:id:wootan1102:20160418011901j:plain
もちろんパソコンもあります

f:id:wootan1102:20160418000236j:plain
足湯コーディング

f:id:wootan1102:20160418011825j:plain
せっかくなので見晴らし茶屋で休憩?

夕食(1日目 19:00)

アプリチームはこの時点で実装はほぼほぼ終わっていました。
残りはコードレビューと動作確認のみです。
夕食もとても美味しかったです。
ここでは一旦作業のことは忘れ、先日おこなった勉強会の話題で盛り上がりました!

f:id:wootan1102:20160418012109j:plain

f:id:wootan1102:20160418012301j:plain

f:id:wootan1102:20160418013317j:plain
ビールで乾杯

開発再開(1日目 21:00)

お風呂に入り開発再開です。
会議室は0時までの利用だったので部屋に戻って作業しました。
部屋でもWiFiが使えたので快適に作業できました。

f:id:wootan1102:20160418014217j:plain
リラックスしながら開発

f:id:wootan1102:20160418014305j:plain
深夜の様子

朝食・散歩(2日目 09:00)

朝食はバイキング形式でした。
朝食後は箱根ベーカリーでカフェラテを買うついでに散歩

f:id:wootan1102:20160418014723j:plain
遅くまで作業していたので眠そう

f:id:wootan1102:20160418014756j:plain
箱根ベーカリー(2回目)でカフェラテを購入

f:id:wootan1102:20160418014849j:plain
景色も最高

f:id:wootan1102:20160421191312j:plain
癒やされます

デバッグ(2日目 10:00)

アプリチームは動作確認を終え、成果発表用の資料も完成!
しかし、Webチームで作業がコンフリクトしてしまいバタバタ
チェックアウト時間になってしまい発表会をやる時間がとれませんでした。
成果発表会のみオフィスで行うことに。。。

BBQ(2日目 13:00)

テルチェックアウト後にみんなでBBQ!
準備や後片付けをすべてやってくれるので便利
僕の愛用するCHUMSのショップもありました!
www.herofield.com

f:id:wootan1102:20160418113100j:plain
BBQスタート

f:id:wootan1102:20160418113112j:plain
登山が趣味のデザイナーが焼いてくれています

f:id:wootan1102:20160421191508j:plain
フランクフルト最高

f:id:wootan1102:20160421191546j:plain
美味しい

f:id:wootan1102:20160418113122j:plain
BBQ中でもパソコン

f:id:wootan1102:20160421191409j:plain
ダッチオーブンスチーム

成果発表会

合宿中にできなかった成果発表はオフィスで行いました。
今回行った「マテリアルデザイン対応」は後日まとめて紹介したいと思います。

f:id:wootan1102:20160418112620j:plain
発表の様子

反省点

  • 前半 Webチームのデバッグを手伝えなかった
  • 時間をしっかり切って発表会をするべきだった

良かった点

  • 差込タスクがなく快適
  • WiFi / 会議室が自由につかえて良かった
  • 合宿前に少しだけ着手していたので時間通り終えられた
  • 景色が良くリフレッシュできた
  • 足湯のおかげで(?)不具合が解消した
  • メロンパンが美味しかった

開発合宿を振り返って

今回relux開発チームでの開発合宿は初の試みでした。
やってみると想像以上に集中して取り組めたのではないかなと思います。

普段あとまわしにしてしまっている気になる部分を
改修できエンジニアとしても嬉しかったです。
やりたいことやチャレンジしたいことは他にもたくさんあるので
次回の開発合宿までにネタをいろいろ考えてみたいと思います。

リラックス&集中できる環境を用意していただきありがとうございました。

f:id:wootan1102:20160418020327j:plain

relux iOSアプリにNavigationDrawerを実装

relux iOSアプリにNavigationDrawerを実装しました。
いくつかライブラリも検討したのですが、条件に会うものがなくスクラッチ開発しました。
その時に気をつけたことや工夫したことを紹介したいと思います。

NavigationDrawerとは?

f:id:wootan1102:20160408021018g:plain

画像のように左側からスライドして表示されるメニューがNavigationDrawerです。
詳しくはGoogle design guidelinesを参照
Navigation drawer - Patterns - Google design guidelines

求める要件

  • Androidと同じような挙動になること
  • デザインを自由に変更できること
  • メニューの順序を簡単に変更できること
  • 画面端をスワイプした際に指についてくること
  • メンテナンスされていること(最新のiOSに対応していること)

レイアウト

レイアウトはあとから変更になる可能性が高いので
Storyboard(AutoLayout)を使用しています。


左側のメニュー部分はUITableView、右側の透過している部分はUIViewです。
透過している部分はTouch時にドロワーメニューを閉じるようになっています。

ユーザ情報セル

背景画像は季節毎に変更できるようにサーバから取得するようにしています。
頻繁に変わるものではないですが変更した際にすぐに反映させたかったので
画像のキャッシュ期間は1日としています。
ここでは画像の上に文字をのせるので視認性をあげるためにグラデーションをかけています。

// グラデーションをかける処理 UITableViewCellなどでは複数回呼ばれないように注意
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = CGRectMake(0, 0, view.frame.size.width, view.frame.size.height);
gradient.colors = @[
  (id)[UIColor clearColor].CGColor,
  (id)[UIColor blackColor].CGColor
];
[view.layer addSublayer:gradient];

イコン画像は丸くトリミングし、名前と会員IDをアイコンの横にならべて表示しています。

// 画像を丸くトリミング
view.layer.cornerRadius = view.frame.size.width * 0.5f;
view.clipsToBounds = YES;

名前のフォントサイズはかなり大きめに設定していますが
スペースを十分に設けているので実機でみると違和感はありませんでした。

メニューセル

メニューのアイコンはGoogleのMaterial iconsを使用しています。
Androidアプリでよく使われるアイコンですがiOSで使用しても違和感はありません。
ただし、システムアイコンとならべるとボーダーが太いので気をつける必要があります。
design.google.com

メニューセルではRippleEffect(波紋アニメーション)はあえてつけませんでした。
技術的にはつけることは可能なのですが、
iOSらしくない動きであること
古い端末を考慮するとスペック的に厳しいことから実装しませんでした。

レイアウト順序

メニュー部分のレイアウト順序は追加・変更になることがわかっていたので
担当エンジニア以外でも簡単に変更できるように定数で持っています。

#define NAVIGATION_DRAWER_USER_INFO_ROW                  0
#define NAVIGATION_DRAWER_USER_BOOKING_CONFIRMATION_ROW  1
#define NAVIGATION_DRAWER_USER_FAVORITE_ROW              2
#define NAVIGATION_DRAWER_USER_HISTORY_ROW               3

スピード優先で実装しているとハードコーディングしてしまいがちですが
あとで困るのは自分なので こういう部分は意識して定数にするようにしています。

CustomTransitionの実装

今回使用したのは以下になります。

UIViewControllerAnimatedTransition

  • transitionDuration
  • animateTransition

UIPanGestureRecognizer

UIPercentDrivenInteractiveTransition

実装方法についてはAppleドキュメントに記載されているので
紹介はしませんが工夫した点があるので紹介します。
developer.apple.com

指にあわせて動かす工夫

実装時に一番苦労した部分でもあります。
右側の透過部分を含めて1画面としているので
そのまま実装してしまうと指についてこないように見えてしまいます。

そこで画面の幅から計算して擬似的に指についてくるように実装しました。
offset分だけずらしてcontrollerの横幅で除算することで割合を算出しています。

case UIGestureRecognizerStateChanged: {
  CGFloat width = controller.view.bounds.size.width;
  CGFloat fraction = MAX([gesture translationInView:controller.view].x + offset, 0) / width;
  if (fraction > 1.0f) {
    fraction = 1.0f;
  }
  _shouldCompleteTransition = fraction > INTERACTION_THRESHOLD;
  [self updateInteractiveTransition:fraction];
}

やってみて感じたこと

今までCustomTransitionはあまり使っていなかったのですが、実装してみるとそんなに難しくありませんでした。
CustomTransitionを実装るすことによって他のアプリとは違った動きになるので、他の画面でもどんどん取り入れていきたいと考えています。

良かったら実際のアプリを触ってみてください!
いつものreluxを、アプリでも。 | relux