もちゅろぐ

iOSやSwift、モバイル設計だったりRailsについてまとめていく

WebフロントエンドエンジニアとiOSエンジニアとAndroidエンジニアが最低限知るべきマルチプラットフォーム対応サービスの仕様とデザインの共通と独立の話

背景

自分はUI/UXデザイナーでもグラフィックデザイナーでもないBtoC向けサービス作るのが好きなエンジニアです。 今回会社でクライアントエンジニア向けにプラットフォーム横断するサービスのおいてプラットフォーム横断すべき情報とすべきではない情報について考える機会があったので記事にしました。

質問

みなさんはiOSなのにAndroidみたいなアラート、 AndroidなのにiOSみたいなダイアログボックスをデザイナーから依頼されてアラートやダイアログをフルカスタマイズして実装した経験はないだろうか?

私も経験あります。 あれ意味分からないですよね。 メリットよりデメリットの方が大きいです。

例えばアラートと言われたら最初に浮かぶイメージは何ですか?

  • Webフロントエンドエンジニア「ブラウザ上部に表示されるしょっぱいボックス」
  • iOSエンジニア「画面中央にポップアップで出てくるいわゆるダイアログボックス」
  • Androidエンジニア「iOSと同じくダイアログボックス」

もう一つ、エラー通知と言われたら何が浮かびますか?

  • Webフロントエンドエンジニア「エラー文言を帯やフォーム毎の下に表示。またはCSSゴリゴリの自作ダイアログボックス」
  • iOSエンジニア「アラート、もしくはサードパーティの自作ビュー」
  • Androidエンジニア「アラート、もしくはトースト」

表現方法はプラットフォーム間で比べると大きく異なるが、プラットフォーム内で比べると統一されてる

このように同じアラートやエラー通知と言ってもプラットフォームによって表現方法は一致しません。 だけどその表現方法はプラットフォーム毎のユーザーからしたら標準で見慣れた物です。

つまり プラットフォーム間と比べるとバラバラだけど、アプリ内の表現方法は一致してますし、他アプリでも見たことある表現方法です。

この前提の上で次の仕様があったら

「ユーザーに入力不備エラーを通知する」と仕様があったら、各プラットフォームの表現方法は違います。

  • Webフロントエンドエンジニア「入力フォームの下に赤文字で文言を表示する」
  • iOSエンジニア「アラート(UIAlert)、自作ビューで文言を表示する。文字色は通常色だがエラーアイコンがついてたりする」
  • Androidエンジニア「アラート(AlertDialog)、トーストでiOSと似たように表示する」

つまり仕様は3プラットフォーム同じだけど、仕様の表現は3プラットフォーム異なります。

補足:各プラットフォームの表現方法は一例です

例に上げたプラットフォーム毎の表現方法はトレンドやデザインコンセプトによって変わります。 しかし各アプリ内においては統一されています。

もう一度質問、Web, iOS, Android も全く同じダイアログボックスをデザインして使うメリットはあるか?

ない

たとえあったとしても、そのプラットフォームの標準やトレンドからズレた表現をユーザーに見せることで ユーザーはその表現への学習コストがかかる

例えば

アラートのYes/Noボタンの配置はプラットフォームによって異なるバージョンが存在します。 iOSユーザーにAndroid向けのYes/No配置を見せると、iOSユーザーは操作を間違える。 なぜなら他iOSアプリとYes/No配置が逆だから。 これはUX最悪です。

例2

Macアプリであるアプリだけウィンドウ左上の 「閉じる/最大化/最小化」ボタンが右上にあったらどう思うかと同じです。

では仕様満たせばデザインは完全独立していいのか?

サービスデザインにおいてテーマカラーに違反して、サービス全体としての一体感が薄れます。

例えばピンクをベースカラーとしたサービスがあったとする

  • そこにLPのデザインの全体色が水色だったら?
  • 外部のクレカ決済サービス使うためそこへ遷移したら青色だったら?

もっと簡単に言うと

cookpadのWebアプリ版とiOSアプリ版を見れば分かります。

そもそもデザインは誰向けなのか考える

  • AndroidiOSのデザインをあわせた所で、Androidアプリを使うユーザーからしたらiOSのデザインが合わさってる恩恵は受けません。
  • WebとiOSのデザインをあわせた所で、iOSユーザーは見慣れない操作を体験しなければいけません。
  • PC版WebとiOSデザインをあわせた所で、ポインティングデバイスが違いすぎてUXは最悪です。

各プラットフォームのユーザーに快適で見慣れて受け入れやすいデザインにしていくべきです。

だけど仕様はプラットフォーム跨いで合わせないと不便になる

WebのSEO強化でユーザー集客し、アプリ版へ導線ひく方法は一般的だと思います。

「Webで使ってみて便利だからアプリも使ってみよう」と思ったユーザーがアプリを使ったところ 「Webで使ってた機能がアプリではない」「Webで覚えた用語がアプリじゃ全然違う用語で使いにくい」 といった残念なUXになります。

まとめ

上記を整理すると

  • 全体のデザインガイドラインは全プラットフォーム準拠すべき
  • 仕様やUXの表現はプラットフォーム毎に適切でかつ統一した方法にすべき
  • 仕様やUXはプラットフォーム横断で合わせるべき(ただし独占仕様などは例外)

画面外へスライドアニメすると表示が消えてからスライドするのはSafeAreaが原因

問題の症状

下のアニメのように外へスライドしようとすると最初に白くなってから上へスライドします。

画面外へスライドアニメで表示が消える
画面外へスライドアニメで表示が消える

コードは下記のように至ってシンプル。

UIView.animate(withDuration: 0.3, animations: { [weak self] in
    guard let self = self else { return }
    self.frame.origin.y = self.parentFrame.origin.y - self.frame.size.height
}) { [weak self] (_) in
    guard let self = self else { return }
    self.removeFromSuperview()
    completion()
}

View階層も次の通り

UIViewController.view
  |
  +-- MKMapView
  |
  +-- Button
  |
  +-- SearchPopupView (これをスライドさせてる)

Auto Layoutが効いてる場合は、 animations クロージャ内に self.updateConstraints() が抜けてるとかありますが、今回は対象View自体にはAuto Layoutは使わず単純に UIViewController.view に addSubview してるだけです。

原因

スライド対象Viewの内部ViewのAuto LayoutでTopをSafe Areaに繋いでいることが原因でした。

対象View 制約

今回のようなパーツxibのような Safe Area を使わなくても良い場合はSafe Areaを無効にすることで 画面外へスライドアニメしても表示が消えずにスライドできるように解決できました。

cronの書き方

crond だけでなく何かと色んな所で出てくる cron 記法 毎回検索しても欲しい記法見つけるのに時間かかるのでほぼ自分用に書いた。

フォーマット

分 時 日 月 曜日 コマンド

日時の範囲

時間 値範囲
0~59
0~23
1~31
1~12 or jan~dec
曜日 0~7 or sun ~ sat

* はどの値にも当てはまる

* * * * * <command>

, で区切ると複数時間を指定できる

月曜と水曜のみ実行

* * * * 1,3 <command>

*/ で割ると一定時間おきに実行する

3時間置きに実行

* */3 * * * <command>

※ この記法は Linux だと動かないかも

古いPCにLinux(CentOS 7)をUSBメモリでインストールしてプリンターサーバーとして再生させる

こないだ断捨離してたら独身の頃買ったネットブックを発見。 長らく使っておらず今後も予定なかったので、破棄も考えたのですが比較的使っているプリントサーバとして勉強がてら復活させました。

色々ネットで調べながら構築したのですが、ネットでは見つからなかったり、躓いたり、情報が散らばって進めにくかったのでまとめました。

環境

機器 名称
PC acer の Aspire 1410
プリンター iP2700
OS Windows 7 64bits版
作業PC Mac
USBメモリ 4GB

構築したプリントサーバ

Windows 7 がインストールされてる 内部HDD を CentOS 7 64bits Minimal で上書き。 GUIはなしです。 プリンターはPCにUSBケーブルで接続します。 system-config-printer は GUI なのでそれ以外の方法で構築します

注意

PC内のデータやOSは全て削除されます。

イメージファイルをダウンロード

特別記載することはないですね。 CentOS のページからダウンロードするだけです。 Download CentOS ちなみに Minimal にした理由は2つあって

  1. 手持ちのUSBフラッシュメモリの容量不足
  2. 足りないパッケージや環境などを自分で構築するため

のためなので、特にそういった目的不要であれば フルパッケージ版を入れてもいいと思います。

イメージファイルをUSBフラッシュメモリに書き込む

USBメモリの初期化

ディスク一覧を確認

$ diskutil list

MS_DOS(FAT)形式で初期化 ディスク一覧で確認したパーティション/dev/disk2 だとしたら

$ diskutil eraseDisk MS-DOS UNTITLED /dev/disk2

マウント解除

$ diskuntil unmountDisk /dev/disk2

ISOイメージをUSBメモリに書き込む ここで Finder 上でisoイメージファイルをコピペしても認識されません。 ddコマンド使って書き込む。

以下は isoイメージファイルが ~/Downloads/centos.iso とした場合

$ sudo dd if=~/Downloads/centos.iso of=/dev/disk2 bs=4028

ディスク取り出し

$ diskutil eject /dev/disk2

BIOSのブート順序を変える

  1. PCの電源を入れてF2を押しておき、F12が押せるように設定を変更
  2. 再起動してF12を押しておき、ブートディスクをUSBメモリを1番上に移動

yum を使う準備

$ yum update
$ yum install epel-release
$ yum install wget

権限エラーの場合はsudo つけてください

CUPSをインストール

$ yum install cups cups-devel

権限エラーの場合はsudo つけてください

次のような必要ファイルが見つからない場合は

エラー: 依存性の欠如:
    libcups.so.2 は cnijfilter-common-3.30-1.i386 に必要とされています
    libpopt.so.0 は cnijfilter-common-3.30-1.i386 に必要とされています
$ yum provides libcups.so.2

のようにすることで内包してるパッケージ名を確認できます。

PCからプリンタのUSBケーブルを抜くとCUPSの設定が無効になる

CUPSウェブ管理上では変化ないが印刷しても反応しない 再設定すると印刷される

Macのプリンタ設定で見つからない場合

プリンターサーバーに avahi をインストールしてみてください。

CUPSを設定

vi /etc/cups/cupsd.conf

Listen localhost:631

# Listen localhost:631
Listen 631

にする

<Location /></Location>

の間に Allow From All 末尾を追加

<Location /admin></Location>

の間に Allow From All 末尾を追加

<Location /admin/conf></Location>

の間に Allow From All 末尾を追加

CUPS起動

プリンタ電源を入れておこう

USBにプリンタが接続されているか確認

lsusb
$ systemctl start cups

Firewalld で IPP のポートを開ける

サービス確認

$ firewall-cmd --get-services

IPP が定義されてれば追加

$ firewall-cmd --add-service=ipp --permanent
$ firewall-cmd --reload

Linux用プリンタードライバーをインストール

キヤノン:ダウンロード|IJ Printer Driver Ver.3.30 for Linux から rpm をダウンロードします

$ wget http://pdisp01.c-wss.com/gdl/WWUFORedirectTarget.do?id=MDEwMDAwMjcxNjAx&cmp=ACM&lang=JA

ダウンロードしたファイルはホームフォルダにあります。 適宜名前を変更してください。

$ rpm -vhU --nodeps --force <rpm name>

CentOSの再起動が必要です。

CUPS設定

ブラウザで http://サーバーアドレス:631 にアクセス サーバーアドレスは

$ ip a

で確認できる

  1. 管理者向けの「プリンターとクラスの追加」
  2. プリンターの「プリンターの追加」
  3. 権限エラーページが出るので表示されたURLにアクセス
  4. 再度「プリンターの追加」
  5. rootアカウント情報を入力
  6. プリンターに接続しているローカルプリンターを選択
  7. 「このプリンターを共有する」をONにする

PCのスリープをOFF

vi /etc/systemd/logind.conf を編集する

#HandleLidSwitch=suspend
#HandlePowerKey=poweroff
#HandleSuspendKey=suspend
#HandleHibernateKey=hibernate

# PCを閉じた
HandleLidSwitch=ignore
# パワーキーを押した
HandlePowerKey=ignore
# サスペンドキーを押した
HandleSuspendKey=ignore
# ハイバネートキーを押した
HandleHibernateKey=ignore

再起動する

$ systemctl restart systemd-logind.service

プリンタの自動電源OFF機能を無効化

iP2700だと自動電源がデフォルトでONになってる 調べた感じCUI上からだと変更はできなさそう。

少しセキュリティを強固にする

yum-cron で定期 yum update 実行

$ yum install yum-cron

除外設定 vi /etc/yum.conf

[main] より下に除外したい項目を追加

[main]
exclude=kernel*

自動更新を設定 /etc/yum/yum-cron.conf

apply_updates = no

を yes にする

セキュリティ関連に限定するために

update_cmd = default

を security にする

自動起動にする

$ systemctl start yum-cron
$ systemctl enable yum-cron

不要サービスを止める

起動中サービスを確認

$systemctl list-unit-files -t service 

サービスを止める

$ systemctl disable <service name>

root ユーザーのログイン無効化

/etc/ssh/sshd_config 内の

#PermitRootLogin yes

PermitRootLogin no

に変更する

再起動する

$ systemctl reload sshd.service

何かインストールしても変わらない場合

OSの再起動を試してみてください。

参考URL

Mac OSX上でISOイメージからBootable USBを作成する - 1日ひとつだけ強くなる SambaとCUPSと各種プリンター(CUPS・プリンタ編) - Qiita 【丁寧解説】Linuxのファイアウォール firewalld の使い方

CUPS 設定 - CentOS プリンタ 管理

CircleCIで別途SSHキーを追加して使う方法

最近CircleCIを使って定期実行的なことをするために弄り始めました。

CircleCI が自動で生成&登録してる鍵は read-only

CircleCIを使ってサーバーにデプロイしたい場合やGitHubへpushしたい場合は、 自動生成&登録されるSSH Keyでは権限が足りずエラーになります。

別途SSH Keyを登録して利用することで上記のような目的を達成できるようになります。

手順

  1. ssh-keygen -m PEM -t rsa などで鍵生成
  2. GitHubのrepository設定のdeploy keyに公開鍵を登録
  3. repository毎の設定 > PERMISSIONS > SSH Permissions へ移動
  4. Add SSH Key を押下
  5. ホスト名を github.com
  6. Private Keyに秘密鍵を登録
  7. .circleci/config.yml の steps 内で add_ssh_key コマンドを記入

add_ssh_key に関してはこちら参照。 記事中に出てくる Fingerprint は GitHubかCircleCIのrepository設定ページで確認可能。

関連

CircleCIにSSH Key登録が失敗する場合はこちらをどうぞ mothulog.hateblo.jp

CircleCIでSSH Key登録が失敗する場合の原因特定方法と自分が見つけた解決方法

最近CircleCIを使って定期実行的なことをするために弄り始めました。

CircleCIの簡単な説明

CircleCIはGitHubのレポジトリベースで管理されており、セットアップすると自動でread-onlyの鍵がGitHubの対象レポジトリに登録されてます。 これによりCircleCIがGitHubのレポジトリをgit pullして取得したコードのテストを実行できるようになります。

自分が試したかったのは少し違う

自分がやりたかったのはgit pull後にテストではなくバッヂスクリプトを走らせた後に 成果物を再度対象レポジトリにgit push することでした。

自動で登録される read-only な鍵では足りないので新たに鍵を追加する必要があります。 そのさいに何故か秘密鍵が登録できず失敗になって躓いたのでその時に調べたことを紹介しようと思います。

SSH Key 追加画面 f:id:motom552:20181126162504p:plain

エラーはブラウザコンソールに出る

f:id:motom552:20181126163343p:plain

エラーになると失敗しか表示されず一見まったく原因が分からないですが、 ここでエラーになったらブラウザコンソールでエラーログに失敗要因が表示されてます。 Chromeであればoption+command+iで呼び出せます。

自分が躓いたのはエラーログでは分からず

しかし自分が躓いたのはエラーログが単純に400が変えるだけで詳しい原因が分からなかったのですが、 次の方法で解決できました。

SSH Keyを登録する時に

$ ssh-keygen -t rsa -b 4096 -C "メールアドレスなど"

のようなコマンドだと思いますが、これに -m PEM をつけて強制的にPEM形式にすることで無事登録できました。

$ ssh-keygen -m PEM -t rsa -b 4096 -C "メールアドレスなど"

参考

discuss.circleci.com discuss.circleci.com

クラス名にSimpleやEasyがついててもクラス内コードが単純なわけではない

クラス名は目的やできることや塊を表現する。
そのためクラス名にSimpleやEasyがついてる場合

  1. 提供してる機能自体がシンプル
  2. 提供してる機能の使い方がシンプル
  3. 保持してるデータ構造の名前が「シンプル」とネーミングされている

と少なくとも3つの解釈ができる。

ここで、クラス名にSimpleだからといってクラス内コードがシンプルと思うのは、
設計において

  • 「できること」とその「実現方法」の隠蔽化による関心ごとの分離を理解できていない
  • クラス名の活用意図を間違えている

と言える。どの解釈においてもコードがシンプルと断言には至らない。

「3.がそうじゃないのか?」と思っても、仕様・要件上「シンプルな方」だからシンプルと名付けてるだけであって 絶対にコードがシンプルになるわけではない。

データ構造がシンプルだからコードもシンプルと言うのは、偶然コードもシンプルになりやすいだけである。

提供してる機能自体がシンプル

何か高機能なサービスをラッパーして使いたい機能のみを提供するクラスなどによく見かける。

提供してる機能の使い方がシンプル

スレッド操作や並列処理、直接扱おうとするとコードが複雑になり、ボイラープレートコードなどで埋め尽くされるのを1つのクラスに隠蔽化することで、手段を簡単に扱えるようにさせる場合に使われやすい。

保持してるデータ構造の名前がシンプル

データクラスにおいて保持するデータ構造の名前が「シンプル」とついてる。つまりドメイン用語でシンプルとついてる。