React Hooks を雑にメモってまとめる vol.1

超訳・React歴史的経緯

  • Class Base ComponentからFunctional Componentへ
  • SFC(Stateless Functional Component) と関数の入れ子(compose)で振る舞いと見た目を決める
  • State管理とRenderingライフサイクルとしてReduxが関数ミルフィーユを支える
  • React本体が振る舞い自体を関数化しちゃえ!からの React Hooks 爆誕

React Hooks API

https://reactjs.org/docs/hooks-reference.html

useState

const [state, setState] = useState(initialState)

  • state: 状態変数
  • setState: 状態を更新する関数
  • initialState: 状態変数の初期値

SFCにState(とそのupdater)を持たせるための関数。SFC + S = FC。 stateの更新を望む振る舞いごとに、updaterを利用した関数を定義してexportしてあげればView側でその関数をbindするだけでよい。

1
2
3
4
5
6
7
8
export const useCount = () => {
const [count, setCount] = React.useState(0);
const add = () => setCount(count + 1);
const reduce = () => setCount(count - 1);
return { value: count, add, reduce };
};

export default useCount;

サンプル

useEffect

useEffect(実行する関数[, 変更を監視するStateの配列])

Rendering完了後に実行したいことを詰め込む。componentDidMountとかcomponentDidUpdate的なやつ。
第二引数にどのSteteの変更に由来したRenderingであるかを限定する配列を指定できる。
第二引数を省略した場合はすべてのStateをしたことになる(いつも動く)。
useStateで初期値をセットしたときにもHookされるので、componentDidMount相当のことができる。

1
2
3
4
5
6
7
8
9
10
11
const [data, setData] = React.useState({ count: 0, message: "initialize" });

// define any functions use `setData`.....


React.useEffect(
() => {
console.log(`now value is ${data.count}`);
},
[data.count]
);

サンプル

useContext

useContext(AnyContext)

プロダクト開発ではまあめったに使わないし、使えるほどの設計パワーが足りないのでほとんど使わなそう(読むことはきっとある)。
createContext で生成したPropsを下位のコンポーネントが受け取る場合、従来はConsumerのrender props引数から受け取っていたが、
useContext の返却値として受け取れるようになった。Contextからattributeを取り出すようなイメージ。

useReducer

const [state, dispatch] = useReducer(reducer, initialArg, init);

  • reducer: reducer。 actionを受け取ってstateを返す関数
  • initialArg: stateの初期値。
  • init: stateを初期化する関数。任意引数で、init(initialArg) の形で実行される。明示的に初期化を実行する場合(Lazy initialization)などに使用する。

useSteteと似たAPIだが、updaterではなくdispatch関数を返す。dispatchで指定したActionはreducerに渡される。
reducerは現在のstateとactionの値をもとに更新後のstateを回答する。dispatchが更新後のStateを適用してViewが更新される。

useState が個々のStateに対してupdaterを関数化するのに対して、dispatchが全体のStateの更新手順(Action)を指定する。
書いてる分にはreduxと大差ない。updaterがreducerに集合してState更新のルーティングが見え、振る舞いの隠蔽とカプセル化ができる。よい。

サンプル

useMemo

const memoizedValue = useMemo(() => func(a, b), [a, b]);

memo化関数。第二引数に指定した値に変化がある場合のみ、第一引数の関数を実行する。
ReactではRenderingの過程に必要な計算処理はRenderingが起きるたびに毎回実行してしまうが、useMemoを使って関数を定義しておけば監視対象となる値が変化した場合のみRendering過程の計算を再実行し、値が変化していない場合は実行せずに前回の値を使用してくれる。すなわち、複数のPropsのうち、計算が必要なPropsが変更された場合のみrendering過程でのコンピューティングを実行するように最適化できる。

サンプル

useCallback

const memoizedCallback = useMemo(() => func(a, b), [a, b]);

useMemoとほぼ同じだが、関数オブジェクトそのものをmemo化する。

サンプル

useMemouseCallback のサンプルの違いは

1
2
- <p>msg</p>
+ <p>msg()</p>

だけ。
関数オブジェクトをmemo化することによって、Propsに与えている関数が再生性されることを抑制するなどができそう。


vol.1では一旦ここまで。次回、useRefなどの あまりつかわなそう 使用頻度の低い、Hooks APIにふれる。

エンジニアリングとコミュニケーション

コミュニケーションについて課題を感じる程度におじさんになってきたので思いの丈を垂れ流す。

エンジニアリングとプログラミングの違い

エンジニアとプログラマの違いはなにかということをよく考えた。そして結論がでてずっと同じことを言ってきた。
エンジニアはエンジニアリングができる人だ。プログラマはプログラミングができる人だ。だがエンジニアはプログラム書くが、それよりも重要なのは叶えたいものを現実にする技術をもっていることだ(もの、と書いたけれどものじゃなくてもいい。叶えたいなにかだ。面倒なので今後も「もの」と書く)。そしてそのために必須の能力が、コミュニケーションの能力だと僕は思う。一人の開発者が叶えられることは本当に小さい。分業や役割が必要だ。おなじ専門を持った人たちが集まったとしても、良い議論は生まれるが、よいものは生まれない。ソフトウェアの世界ではこの分業の究極形がある。OSSだ。各々が専門する分野で有用なものをつくり合う。思想を共有する。コードの価値に思想共有というコミュニケーションとしての価値が乗っかったもの、それがOSSだと僕は思う。なのでコミュニケーションができないというのはエンジニアとして致命的な欠陥だと思う。

コミュニケーションを定義したい

「コミュニケーションができない」という判断がある以上、コミュニケーションを定義しなければならない。これが難しいかもしれない。いや、定義は単純である。

ひとつは「相手に意思が伝わること」である。気持ちよく伝える技術があるかどうかは一応のところ関係がない。最悪、とても辛辣で鬱陶しい方法であっても、「伝われば」とりあえずの目標が達成される。
もうひとつは「相手の意思を理解できること」である。傾聴できるかどうかはやはり一応のところ関係がない。最悪、とても無愛想で悪辣で威圧的であっても「理解できれば」とりあえずの目標が達成される。

ではなぜこれが難しいのか。この定義自体が人によってブレてしまうからかもしれない。相手に意思が伝わったと判断する能力が正しく機能しないと、もしくは相手の意志を理解したと言うことを表現できないと、意思交換が成り立たないのだ。僕は相手の意思を理解できたかどうか不安になったときに、言い換えて同じことを言う癖がある。「イヌはネコ目に属する」と言われたら、「ネコ目にはすべてのイヌが属していて、ネコ目以外のイヌはいないと言うことですか?」とか言う。これを嫌う人もたまにいるが、癖なので直せない。意思交換ができたと納得できないと、何度でも言い換えをして訊いてしまう。たまに家族にこれをやって嫌がられることもある。しつこいやつだ。

エンジニアリングコミュニケーションの閉塞感

エンジニアにコミュニケーションが必須なのは上記の通りなのだが、その手順というか、その方法がどうも社会受けが悪い。僕の言い換え質問が家族に嫌がられるのと同じように、社会でもたびたび嫌がられることがある。これはエンジニアという人のコミュニケーションが悪いという話ではなく、エンジニアリングのコミュニケーションに特殊性があるということを言いたい。エンジニアはただ、知りたい。まず知って、事象を整理して、思考したい。それが生きる術だし、そうして自分をパワーアップしてきたという自負があるからだ。また、いま起きていることに無頓着なようにも見られがちなのも悪く働いていそうだ。起きてしまった悪いことに対して、焦りや憤りを表面にださない癖がついている。埋没損失について、よく理解しているからである。

だから、冷静淡々と事象そのものを深く掘ろうとする。傍からみるとそれは、いわゆる叩いているように見えるのかもしれない。ある文化圏には、ミスをしたひとには「まず優しい言葉をかけてあげる」というのがよいコミュニケーションとされているようだ。でもチームとして機能するために、それほど価値を感じないので仕方ない。

叶えたいものを現実にする技術をもつことは、現実を把握することが最重要だからであるし、仮にミスしたのが自分でも欲しいのは優しい言葉ではなく、復旧するための筋道だからだろう。それらが終わった後に、自分のミスを謝罪し、それでもチームのひとりとして受け入れてもらえるようなそういう言葉をひとつでも貰えれば、安心してベッドに入ることができる。こういうコミュニケーション手段が僕には居心地がいいのだけれど、やはりどうしても受けが悪い場面も多い。

なんでだろう

なんでこうなるんだろうと考えていろいろ読んだりした先に、すこしのヒントを得た。実は、コミュニケーションには2つの面がある。ひとつはいままで書いてきた「思想の共有・意志の交換」の手段というもの。もうひとつは「敬意の表現や信頼の獲得」の手段というもの。ミスをした人にまず優しい言葉をかけるというのは、後者からくるものである。心理的安全を与えることで信頼を高めて、目の前の障害に立ち向かおうという手法らしい。誤解を恐れずに言うと、子育てみたいだな、と思った。相手を落ち着かせて、正しい意志を引き出すということは、父親としての僕もやってきたことである。だがその過程がどんな場合のコミュニケーションでも必要だと考えるひとがいるということには思慮がまわらなかった。そうかー。そうだったのかー。

まとめ

まだまとまらない。

フルリモートワーカーになって1年たった

前回 から更に5ヶ月弱が経過し、フルリモートワーカー開始&入社から1年以上が経過したので、これまでの振り返り。

良かったこと

  • 1年間のなかで、自分の仕事や立ち位置を持つことができた
    • 言語や技術領域が違う会社だったので、ほぼ素人レベルだった
    • 入社前に、自分がもっとも頼られる領域を1年以内に作ろうという目標をたてた
    • 小さくて地味な作業を少しずつ積み上げたり、挫折して泣きそうになったりした
    • おちこんだりもしたけれど、私はげんきです
  • 生活が楽になった
    • 小回りがきく生活は非常に効率がよい
    • 子どもたちと向き合うタイミングを適度に取れた
    • 所得

嬉しかったこと

  • 能動的に動くことに何らかの反応をもらえた
    • 決して正しいやり方ではないものもいくつかあった
    • その度に正面から意見をもらえたし、そのことで本気になれる部分があった
    • 労働時間だけでなく、やること自体に裁量を感じた
  • 出社したときに、自分のキャラを認識していじってくれたとき
  • 入社までの過程が実は珍しいパターンだったと知ったとき

悔しかったこと

  • 人を動かす能力の限界を感じるときがあった
    • 想いを吐き出すことはできたが、落とし所を提示できない
    • ドキュメンテーションがすごく下手(日本語)
    • “正論だけど現実的に厳しいもの” を論理的に否定できない
  • 技術不足
    • 対象領域へのバックグラウンドが浅いこと
    • 対象領域への技術的すそ野が広くないこと
    • 応用的な思考

悲しかったこと

  • 自分に見えてないものが、まだまだあると認識したとき
  • 「フルリモートのひと」と紹介されたとき

苦しかったこと

  • 自分の方向性を見定めて納得するまでの時間
    • 必要とされているのかわからなくなった
    • やりたいことをしているのかわからなくなった
    • 貢献する内容に対して、変により好みしたり固執したりすることがあった
  • 存在としてリアル/ネット、Public/Privateがあいまいになってしまったとき

成長したこと

  • Typescriptを普通に読み書きできるようになった
  • プロダクトが提供する技術領域のコアな部分について理解できるようになった
  • 多少は開発センスがよくなったつもり

これから

  • 居場所をみつけて安心した反面、慢心気味なので謙虚さと貪欲さをなくさないように。初心。
  • 文章を書くなど、人に伝える努力をしたい。
  • せっかく近くにたくさんの能力をもったひとがいるので、そこからまたすそ野を広げられるように盗みたい。
  • 35歳定年まであと2年。

おわり。

フルリモートワーカーになって半年ちょっと経ったのでまとめる

突然ですが、僕は青森県でフルリモートで働いている。出社は東京のオフィスに月に1回、だいたいが1泊2日。転職したのは2017年の7月で、今日で約7ヶ月半が経った。そろそろフルリモートについて軽くまとめておこう。
さらに突然ですが、僕には嫁さんがおり、その嫁さんはWebデザインやUI/UX系のお仕事をしており、むかしから家でフルリモートをしている。もともとはフリーランスだったのが、今は社員としてフルリモートをしているので、 フルリモート社員夫婦 という割りと稀有(だと思っている)な環境になりました。※会社はまったく別です

転職についてはそれほど言いたいこともないので、フルリモートそのものについてちょっとまとめておこうとブログ記事をおこした次第です。


トレードオフ

得たもの

  • 自分にあった椅子
  • 自分の気分で決められる音楽、音響
  • 自分にあった室温・湿度調整

失ったモノ

  • 仕事を切り上げるタイミング
    • 気になったり、修正方法を思いついたりすると、家のどこでも数秒でオフィスに行けるのと同じ。結果、煮詰まって辛くなるので、強い気持ちで「今日は終わり」とする。
  • 早起きに対するモチベーション
    • 出社に公共機関も使わないし、雪かきしないと車が出せないとかもない
    • こどもたちを送り出すためだけに起きる

フルリモートは楽か

  • 仕事に付随する仕事ではない活動が圧縮されるので、圧倒的に楽。
    • 電車、徒歩、車による通勤
    • 外食主体の昼食
    • 職場によっては電話や外来の対応
  • ただし、仕事でない活動の中に楽しさがある人はむしろ苦しいのかもしれない。
    • 通勤がないので何気ない外部の変化を感じない。桜が咲いた、とか。
    • 偶発的に得られる、知らないものへの認知を得ることができない。
  • 体力は必要
    • 出社が完全に不要な環境はなかなかない。平日に長距離移動しながらコード書いたりするのは想像しているよりも体を酷使する。

フルリモートはだれでもできるか

これは賛否両論だろうし、あまり強い言葉で主張しにくいのですが、また、決して自分が頑張っているという自慢の意味ではなく、そんな気持ちを一切抜きにして、 「No」 と言わざるを得ない。ある種の才能なのか、適正なのか、はたまた慣れや経験なのか、リモートが楽な人、そうでもない人、苦痛な人、がそれぞれいると思う。リモートだとチャットが唯一の社内コミュニケーションになることがおおいでしょうが、フルリモートの場合はそれに輪をかけて、チャットで観測できないものは会社で起きてないのと一緒で、 チャットが会社そのもの になる。だから最初の1ヶ月くらいはチャットを常に追いながらコードを書くという脳みそに慣れるのが結構大変だった。できるようになったからいいけども、出来ないひとがいても普通だコレ…というのが正直な感想。
僕の場合は先人のフルリモートワーカーがいてくれたおかげで、そのチャットの呼吸(としか言いようがない)をモデルに自分なりに染み込ませていった。


うまみとつらみ

うまみ

  • 地方だからといって言うほど物価が違うわけではないのだが(同じ日本だぜ)、主に子どもにかかるお金が安いのは助かっている。出入りでいうと割がいい。
  • 節約できる。外にいるとどうしても缶コーヒーとか買っちゃう。家は温かいコーヒーが淹れられる。
  • 思いっきり休憩できる。昼食をとった後に10分くらいソファやベッドで休んだり。

つらみ

  • エンジニア以外との交流が少なくなってしまうのはすこし寂しい。
    • ”袖振り合うも多生の縁”、袖振りあってないしなー。とか。
    • 「また次回」が難しい
  • 事務処理
    • なぜ日本の役所関係各位は、物理的な紙の提出を求めるんですかね。
    • 印鑑文化、そろそろ捨てようぜ

感謝

なんだかんだと書きなぐってみましたが、フルリモート楽しくやってます。ぶっちゃけフルリモートが可能なのって周りのリテラシーとかも必要で、そういう意味では恵まれた 弊社です。だけど周りがそのリテラシーを持ってることに対して「理解してくれて感謝」するのはちょっと違う気がしていて、その辺のコミュ力だったり気持ちは必要なのかもしれません。嫁さんもフルリモート歴が長く、家での仕事のことや体調管理のことに理解があったのもバックグラウンドにあります。ちょっとしたことで人間は割りと簡単にストレスを感じてしまうので、家のなかに仕事を持ち込むどころか家の中を仕事場にするというのはそれなりに考え方をはっきりさせておくのが大事なように思います。 「フルリモートの心得」 みたいな本、もしかしたら需要あるのかも。

Github Appsはいいねぇ。

リリンが生み出した文化の極みだよ。

Github Marketplaceには色々なアプリがある。
使いこなしていい感じに自動化したいし、READMEにgreenのバッジが並んでるとやっぱりいいなぁと思う。
TravisCI/CircleCI/Codecovはもはやメジャー過ぎて、どれも入ってない方が逆にやばい。
言語依存のものもあるので、全部は体験できないなぁとも思う。

学び

  • TravisCIなどメジャーなものも自分の知らない使い方が結構あったりして勉強不足だ
  • privateなもの、secureなモノがあるOrgリポジトリの場合と個人の場合で使い方が結構ちがう
    • その辺も意識していろいろとやってみたほうがいい
    • OSSなら無料のものはそのサービスのplaygroundリポジトリを作るのが良さげ
  • 似たようなサービスでも違いは多い
     - 応答速度や安定性
    • masterに変更が発生した場合の挙動
    • サポート&ドキュメント充実度

全体的に暗黙知になってるものが多い印象なので、Github Apps Meetupとかやったらいいのに(あるの?)

気持ち

コードの学習も大事だけど、使えるサービスを使い倒せるようになりたい。(小並)
できるだけbotに働かせよう。

Hexoにした

あまり気に入ってはないが、あまりにも放置状態でblog環境が死んでいたのでさっと移行できそうなHexoに移行した

気に入ってないポイント

  • templateがejs
  • themeにjQueryが使われてたりする
  • 割りとメジャー

楽だったポイント

  • gh-pageへのdeploy pluginがある
  • RSSも実装できる(atom.xml)
  • wintersmithと同じMarkdownフォーマット
    • 過去記事がコピペで済む
  • CNAME

日本語フォントが割りときれいなのは良いと思った。

TODO

  • customize theme
    • code (syntax highlight)
    • color
  • 3rd party
    • google analytics
    • SNS link

markdown sample

normal text
bold
italic
code

1
2
const message = "hello hexo"
console.log(message)

esa.ioを家庭で使ったらなんとも楽になった話

情報を育てながら共有するドキュメントサービス、esa.io(えさ)を使いはじめて1年以上が経過しました。
初めは嫁さんと一緒に趣味でつくっているナニカについて、タスクや設計、ナレッジを共有するために使う目的でしたが、
最近はもっぱら家のことを日報という形で共有するツールとして使っています。

そこで今回は”えさ”が家庭のタスク管理に非常に優れたツールであることをやや興奮気味に書きたいと思います。

esa.ioと家庭の親和性

すでに家庭での有効性についてはMoney Forwardのエンジニアさんがスライドつきでブログ
を書いていらっしゃるのですが、改めて。

まずはesa.ioのトップにも書かれているえさのコンセプトを。
esa.io は「情報を育てる」という視点で作られた、自律的なチームのためのドキュメント共有サービスです。

家庭はいつもWIP

「情報を育てる」というのは「中途半端な状態から情報共有を開始して、みんなで形にする」ということでしょう。
「中途半端な状態」はつまりWIP(Work In Progress)ですね。

さて、家庭にはいろいろなタスクがあります。

  • 家具家財の購入
  • 連休の予定
  • 保険加入やローン見積もり
  • 親戚の冠婚葬祭
  • 子どもの進学や習い事
  • 保育園のイベントや役員の仕事
  • 地域のイベント準備

もう毎週なにかを決めて予定を組んでいきますが、それでも手が回り切らないことも。
そしてすべてが「未定の予定」で、予定どおり進まないことなんてザラ。予定の組み換えをするのはしかたないのですが、
夫婦ともに同じものを見ていないと認識ズレがあったりしてコストもかさむ。
特に保育園なんかは紙媒体や担任の口頭で情報が来るので、「お互いがその情報を把握しているかを把握する」という多重苦です。
つまり、家庭はWIPのタスクをいくつも並行して抱えるわけですね!!

夫婦はすでにチーム

現代の我々世代の家庭であれば、役割分担はあれども、夫婦両方で家庭のタスクをまわすことが多いでしょう。我が家のように共働きならなおのこと。
つまり夫婦とは、家庭というシステムを保守しながらグロースアップさせるチームなわけです。
我が家は特に役割分担の境界線が曖昧なので、「自律的なチーム」と言えるでしょう。

「漢は黙って」だとか、言っている場合ではありません。「夫婦の阿吽の呼吸」だとか「ツーカーの仲」とか、そういうのは非効率。
さっと共有して、バシッと残しておく。長男のときに解決したことは数年後に次男にやってきます。
数年の間その解決手段を覚えておくことはできません。

家庭タスク管理とツール

もともと我が家は、家庭用のコミュニケーションやタスク管理ツールとしていろいろなものを使っています。

  • Twitter
  • Idobata
  • hubot
  • Redmine
  • github
  • Google Drive

hubotは軽量さとカスタマイズ性が良かったのですが、夫婦の間にbotが入る煩わしさ(どっちがhubotに命令したか、とか)
があって、それほど便利に使えていません。RedmineはiOSアプリもあって便利だったのですが、
家庭のタスクをチケット化すると、チケットのライフサイクルが短い割に大量の件数になってしまい、
運用コストが大変で管理できませんでした。(あと、嫁さんにとって使いなれていないツールだった)

githubはファイルとチケットを紐付けるのが楽なのと、夫婦共に使い慣れているということで
息の長いツールになっています。

家庭用のesaのはじまり

初めは、嫁さんがメモを書いたことでした。
タスクがつまり過ぎて把握できなくなりそうだったので、とりあえずここでいいや、って感じで。
その後も日付を見失わないように日報のテンプレートで、メモ書きが何度かされていました。
僕の方も嫁さんが忘れたときのために内容を把握したり、購入予定の商品を検索してコメントしたりしていました。

そのうち、嫁さんのメモ書きはTODOリストをならべた、ちゃんとした(?)日報になりはじめ、
後を追って僕も考えてることや買いたいもの、覚えているタスクを並べるようになりました。

例えば夕飯の献立だって、冷蔵庫の中身や買い出しの有無、作る時間がどれくらいとれるかで決めているみたい。
WIPの予定を嫁さんが立てれば、僕が食べたいものもリクエストできるし、嫁さんが献立のもう一品に
あれこれ迷う時間も減ります。

家庭用のesaの副次効果

もともとはタスク管理としてのえさですが、別の側面からもいい影響があります。
そもそも家庭が抱える解決すべき対象というのは、合理的な判断だけで決定できるものだけではないので、
ちょっとした摩擦になることも多々あるわけで。
そんなときに感情で議論すると大概、失敗です。もうどっちかが血ヘドを吐くまで、朝まで生テレビ状態。

チャットもよくありません。自分の間違いを訂正できないため、ちょっと混みいった話をするとすぐに
何の話をしてるかわからなくなります。住宅ローンの話を初めたのに、いつの間にか好きな猫の種類の話とかになってます。
2年ほど前の僕のツイートも貼っておきましょう。

はい。ややポエムっぽい文調がキモチワルイですね。
さておき、日報という形で「家庭のタスク」と「考えてること」を共有すると、言い難いことも柔らかく言えるように思います。
「事実」と「感情」が明確に区別して表現できるからでしょうかね。
コメントに引用リプライすることで小さな議論もできますし、書く側は自分の主張を全部だしきってから、読む側は読んだ後にじっくりと
考えてから意見をいえます。これもチャットには無い特性ですね。

まとめ

  • 夫婦は小さなタスクをたくさん抱える、小さなチームである。
  • 感情だけでは家庭はまわらないが、心を込めずに物事を決める家庭も価値が低い。そのバランスをうまーく保てるようにするのが大事。
  • esaの日報形式だと日毎にタスクを共有できるし、じっくりと考えを言い合える。Idobataに日報も通知される。
  • esaならMarkdownで構造化できるので、事実の部分(TODO)と感情の部分(所感)がちゃんとわかれてスッキリ。

Pebble 寿司ゆき Watch Faceをリリースしました

iOS/Android対応のスマートウォッチ、Pebbleを買ってしばらく遊んでいました。
せっかくなので自分で何か作りたいなぁと思いたち、まずは敷居の低そうなWatch Faceに挑戦。

久しぶりのC言語にガタガタ震えながら、LINEスタンプやUFOキャッチャーのぬいぐるみでおなじみの寿司ゆきで廻る寿司WatchFaceが完成、リリースしました!

Pebble 寿司ゆき watch face

寿司ゆきのPebble Watch Faceをリリースしました。
http://apps.getpebble.com/en_US/application/5616705a7e612637e600007f

5種類の寿司が右から左へ流れていき、無限に回転ずしを楽しめます。

![qrcode](/img/pebble-sushiyuki.png)

画面下には充電を%で表示しています。

Pebble

PebbleはKickStarから生まれたiOS/Android両対応のスマートウォッチ。
それでいて、価格は約$200、これは買いですね!!

寿司ゆき

寿司ゆきの画像は、CC-BY-NC-NDですでに提供されている画像を使用してプロトタイプを作っていたのですが、
作者である@awayukiさんのご厚意によりPebble用の回転ずしバージョンをご提供いただきました。
お忙しいなか、大変ありがとうございます。

Pebble 開発環境など

すでに詳しいブログや公式チュートリアルがあります。
開発環境からリリースまではこちらのブログが本当によくまとまっていて有りがたかった!

つぎはWatch Appか、違うタイプのWatch Faceを作りたい!!


iOSのopenURLでハマった

iOSアプリから電話発信をしたい場合にopenURLで標準アプリ(Phone.app)を呼び出すことができる。
それでウキウキと開発していたら発信できない番号に出会い、泥沼にハマったのでメモ。

openURLについて

電話にかぎらず、iOSではURLオブジェクトを開くことでアプリからアプリを呼び出す事ができる。ただし、これは見かけ上の話で、実際には間にURLを解決する
ナニカがiOS上で働いているようなので、Androidでいうインテントとはちょっと趣向の違う仕組みだ。
あくまでもURLを開く処理をアプリで実行すると、該当のURLスキーマを持っているアプリが起動するということで、アプリ自体を指定して起動しているわけではない。

例えばhttp://ならSafari、tel://ならPhone.appがそのスキーマに対応しているので、iOSからURLを受け取って動作する。
電話の場合は下のようにURLを開くことで電話発信が開始する。

1
2
3
NSString *url_string = [@"tel:" stringByAppendingString: @"117"]; //時報にかける
NSURL URL = [NSURL URLWithString:url_string];
[[UIApplication sharedApplication] openURL:URL]; // tel:のURLスキーマに対応する電話アプリが起動

実際にこれで発信自体うまくいくので、大したことはないと思っていたのだが、実際、めちゃくちゃハマった。

1. URLスキーマが曖昧(基礎理解編)

まず、電話アプリのURLスキーマがいろいろあって戸惑う。
調べていると、電話発信のURLスキーマはtel:tel://telprompt://があり、tel:tel://は大別が無く、どっちも同じ挙動となるらしい。
telprompt://は発信をするのではなく、確認ダイアログを表示し、発信ダイヤルを入力した状態で電話アプリを起動させるスキーマで、iOSの公式リファレンスにはないということだ。
リファレンスに載っていないとなると、正常に動作しようと、審査を通過できようと、どうにも使たくない。

2. 電話番号の仕様がよくわからない(困惑編)

電話番号は一般的には090-XXXX-XXXXなど、数字の羅列なのだが、普段使わないそれ以外の文字もある。*#だ。

そして、*#を上の[[UIApplication sharedApplication] openURL:URL]で開くと電話アプリが開いてくれない。
(正式にはアスタリスクや井桁じゃなために)そもそも*/#が発信する文字列として間違っているのか??

しかしiOSの連絡先アプリ(Contact.app)やUIKeyBoardTypePhonePadなどを見てみると、それぞれの文字列が電話番号の入力文字として間違っていないことが確認できた。

余談だが、電話で「*」を押すボタンは「アスタリスク」ではなく、「*」を90˚回転させた記号だ。
そして「#」の名称は「シャープ」ではなく、日本名は「井げた(いげた)」などという。井げたの記号は「#」ではなく「#」である(もう見た目違いがわからない)。

僕のiPhoneでは【しゃーぷ】の変換候補に「#(井桁)」が出てくるし、【いげた】の変換候補に「#(シャープ)」が出てくるし、「*」を90˚回転させたアレは Unicode5.1で採用された特殊文字で普通は使われていないらしい。はー、紛らわしい。

3. ポーズすると発信できる(混乱編)

*#以外にも電話番号には数字以外の文字が存在する。国際番号を示す+や、ダイヤルの発信を一時停止する,である。
ちなみに、,は[ポーズ信号]と呼ばれ、iOSの場合は次の番号の発信まで、発信が3秒間スリープする。
そして*#はこのポーズ信号の後だとちゃんと電話アプリに渡り、電話発信が始まるのである。

oh…意味がわからない。

4. 公式リファレンスの説明が曖昧(解決編)

*#が含まれていると電話発信できなかったので、iOSのリファレンスをちゃんと読んでみよう。
Phone Links

To prevent users from maliciously redirecting phone calls or changing the behavior of a phone or account, the Phone app supports most, but not all, of the special characters in the tel scheme. Specifically, if a URL contains the * or # characters, the Phone app does not attempt to dial the corresponding phone number. If your app receives URL strings from the user or an unknown source, you should also make sure that any special characters that might not be appropriate in a URL are escaped properly. For native apps, use the stringByAddingPercentEscapesUsingEncoding: method of NSString to escape characters, which returns a properly escaped version of your original string.

さっと読んだ感じだと、URLなんだから特殊文字はstringByAddingPercentEscapesUsingEncoding:でエスケープしてって書いている。
実際、多くの方がそう解釈しているのをStackOverflowなどでみたし、僕自身、「Escapeすればいいんだ!」って嬉々としてコードを書いた。
ところが*#が含まれているとやはり発信できない。EscapeにつかっているEncode用の文字コードが悪いのかとか、いろいろ試行してみるも、ダメ。

そこで調べなおしてみると、どうやら上のリファレンスは説明がかなりひどい。

  • 電話のURLスキーマtel:については、*#が含まれていたらURL開かない仕様ですよ。

To prevent users from maliciously redirecting phone calls or changing the behavior of a phone or account, the Phone app supports most, but not all, of the special characters in the tel scheme. Specifically, if a URL contains the * or # characters, the Phone app does not attempt to dial the corresponding phone number.

  • (一般の)URLスキーマを使う場合に特殊文字があるのであれば、URLエスケープしてね

If your app receives URL strings from the user or an unknown source, you should also make sure that any special characters that might not be appropriate in a URL are escaped properly. For native apps, use the stringByAddingPercentEscapesUsingEncoding: method of NSString to escape characters, which returns a properly escaped version of your original string.

ってことらしい(英語圏の方が勘違いしていたくらいなので、英語力の問題ではないと信じたい)。

結論

*#が含まれるtel:はどうやっても開かない。ちなみにURLが開けるかどうかを判定する[[UIApplication sharedApplication] canOpenURL:(NSURL *)]という
メソッドがありますが、こいつはふつーにYESを返すので、チェックとして機能しない。開かないのに。


iOSでQRコードを読み取ってみる

iOS7からQRコードの読み取りが簡単になっていたらしいのですが、そもそも最近はQRコードをあんまり見なくなったのでした。
思い立って(?)、QRコード読み取りのアプリを作ってみたのでメモ。あ、言語はObjective-Cです。

プロジェクト作成

いつもどおり、[New Project]から、[Single View Application]を選択、LanugageはObjective-C.
プロジェクトの[General] > [Deployment Info] > [Device Orientation]でPortraitだけにしておく(面倒なので)。
今日現在でiOS 8.2がでてますが、iPhone6を8.1からアップデートしてなかったので[Deployment Target]を8.1にしておいた。

カメラプレビューはView全体を使っちゃうので、storyboardは特に変更なし。

ViewController.h

カメラ入出力を扱うのでAVFoundation関係のimportと、画像認識のメタデータを扱うdelegateが必要です。
あとカメラのセッションをインスタンスで持っておかないと使えないので、これもプロパティを用意しておきます。

1
2
3
4
5
6
7
8
9

#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>

@interface ViewController : UIViewController <AVCaptureMetadataOutputObjectsDelegate>

@property (strong, nonatomic) AVCaptureSession* session;

@end

ViewController.m

viewDidload

とりあえずviewDidLoadでカメラ周りのインスタンスを生成しつつ、sessionを作成します。
本当はカメラが使えるかー?とかの判定があるべきですが、カメラある前提で書いていきます。
全コードをまず書いてしまうと、こんな感じ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
- (void)viewDidLoad {
[super viewDidLoad];

// Device
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
// session
self.session = [[AVCaptureSession alloc] init];

// Input
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];
if (input) {
[self.session addInput:input];
} else {
NSLog(@"error");
}

// Output
AVCaptureMetadataOutput *output = [[AVCaptureMetadataOutput alloc] init];
[self.session addOutput:output];
[output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
[output setMetadataObjectTypes:@[AVMetadataObjectTypeQRCode, AVMetadataObjectTypeEAN13Code]];


// Preview
AVCaptureVideoPreviewLayer *preview = [AVCaptureVideoPreviewLayer layerWithSession:self.session];
preview.videoGravity = AVLayerVideoGravityResizeAspectFill;
preview.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
[self.view.layer insertSublayer:preview atIndex:0];

// Start
[self.session startRunning];
}

ビデオデバイスとセッション、inputを作る。
previewや[self.session startRunning]特筆することはなく、カメラを使いたいときのいつもの感じです。

QRコード認識

delegateを受けておいて、delegateメソッドで認識した時の処理を定義します。
@[AVMetadataObjectTypeQRCode, AVMetadataObjectTypeEAN13Code]を指定しているとこでQRコードの認識を設定。

1
2
3
4
5
// Output
AVCaptureMetadataOutput *output = [[AVCaptureMetadataOutput alloc] init];
[self.session addOutput:output];
[output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
[output setMetadataObjectTypes:@[AVMetadataObjectTypeQRCode, AVMetadataObjectTypeEAN13Code]];

認識時処理

認識したらcaptureOutputがdelegateメソッドで呼ばれるので、読み取ります。
[(AVMetadataMachineReadableCodeObject *)data stringValue]で文字列化しています。
顔認識の場合とか、他の識別が動作した場合もこのメソッドが呼ばれてしまうので、data.typeAVMetadataObjectTypeQRCodeの ときだけ読み取り文字列をURLとしてSafariで開くようにしています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects
fromConnection:(AVCaptureConnection *)connection
{
// 複数のmetadataが来るので順に調べる
for (AVMetadataObject *data in metadataObjects) {
if (![data isKindOfClass:[AVMetadataMachineReadableCodeObject class]]) continue;
// QR code data
NSString *strValue = [(AVMetadataMachineReadableCodeObject *)data stringValue];
// type ?
if ([data.type isEqualToString:AVMetadataObjectTypeQRCode]) {
// QRコードの場合
NSURL *url = [NSURL URLWithString:strValue];
if ([[UIApplication sharedApplication] canOpenURL:url]) {
[[UIApplication sharedApplication] openURL:url];
}
}
}
}

試してみる

試しにこのブログのURLをQRコードにしてみた。識別されたらこのブログを自動的にSafariで開きます。

![qrcode](/img/qrcode.gif)