2017年6月19日月曜日

UIWebView, WKWebView 等において Drag and Drop を禁止する方法


iOS 11よりDrag and Drop APIがUIKitに追加され、UITextView / UITableView / UICollectionViewに簡単にDrag and Dropを実現するためのdelegateが用意されたり、それ以外のUIViewにもDrag and Dropをハンドリングするための仕組みが用意されました。このDrag and Dropは基本的にはアプリをまたいでデータを受け渡しすることを前提として作られていますが、一応は自分のアプリ内でデータを移動したりコピーしたりするためにも使えるようになっているので、積極的に活用していきたいところです。

ところでこのDrag and Drop、基本的にはアプリ側が対応しない限り自動的には対応してくれないのですが、例外があります。それがUIWebView / WKWebView / SFSafariViewController要するにWebViewのたぐいの皆様です。これらについては最初からDragも可能ですしDropも可能なように作られているようです。便利ですね!

ですがここをご覧になっている皆様の中には、大人の事情によりどうしてもそのような便利な機能をユーザーさんにわざと提供したくないと言うケースがある方もいらっしゃるかと思います。そこでUIWebView / WKWebViewのデフォルトのDrag and Dropを無効化したり、頑張って自分のコードでハンドリングしたりすることができるようにする方法をご紹介します。なおSFSafariViewControllerに対しては手も足も出ないのでご了承ください。
if #available(iOS 11.0, *) { 
    webView.scrollView.subviews.first?.interactions = []
}
たったのこれだけです。簡単ですね!

なんのこっちゃなので詳しく説明します。

まずはじめにiOS 11よりUIView.interactionsプロパティが追加になっています。このinteractionsは[UIInteraction]型となっていて、公式のドキュメントによると「ジェスチャベースの挙動をビューに定義する」ことができるらしいです。まさにドラッグ・アンド・ドロップ的なやつですね。
で、Appleによって最初からドラッグ・アンド・ドロップ用のUIInteractionすなわちUIDragInteractionとUIDropInteractionが用意されています。これらのInteractionは内部的にdelegateを持っていて、これらのInteractionがインタラクションを検知したらdelegateにメッセージングを行い、そいつらが実際のドラッグ・アンド・ドロップ処理を行うというような仕組みになっているようです。
調べてみたところUITableViewのような最初からDrag and Drop用のdelegateを追加されているViewについても、このinteractionsに何らかのUIDragInteraction / UIDropInteractionが追加されていて、それが一旦イベントを拾ってから我々が定義したUITableViewDragDelegate / UITableViewDropDelegateに問い合わせを行うという仕組みで動作しているということがわかりました。

ここまで分かれば後は簡単で、UIWebView / WKWebViewも同様にinteractionsで一旦イベントを受け取った後に、内部的に処理を行っていると考えられるのでUIWebViewの中身をどったんばったん探してみたところwebview.scrollView.subviews.firstの中にinteractionsが刺さっているのを発見したと言う次第です。

先程の無効化する例では空配列を渡すことで完全にinteractionsを無効化していますが、ここで自分が作ったカスタムのUIDragInteraction / UIDropInteractionを渡してやればうまい具合に自分でWebViewのDrag and Drop挙動をカスタマイズすることができるかもしれません。また両方を消すのでなくて片方だけを消せばDragによってデータを持ち出すのは禁止するがDropによってデータを持ち込むのは許可するみたいなこともできます。

2017年5月24日水曜日

Xcode 8.3 & Swift 3.1 環境で LLDB を使う Tips

以前も似たような記事書いた気がするんですが、定期的にSwiftのコード上でLLDBを使ってて困ることがあるので定期的に書くことにします。ほとんど https://stackoverflow.com/questions/29441418/lldb-swift-casting-raw-address-into-usable-type からの転載です。

LLDBの言語を指定して実行する

標準ではbreakpointを挟んでbreakした行がSwiftであればSwift、Objective-CであればObjective-CでLLDBの言語が選択されるようなのですが、これでは毎回毎回breakした地点に応じてデバッグの仕方が変わって大変で仕方がないので、Swiftに統一してしまうのが良いと思っています。
expr -l swift -- {Swift Command}
e -l swift -- {Swift Command}
とすると常にSwiftでLLDBに対してコマンドを送る事が可能になります。逆にObjective-C側に寄せたい場合は
expr -l objc++ -O -- {Objective-C Command}
e -l objc++ -O -- {Objective-C Command}
とすれば良いです。-Oオプションがないと結果がいい感じにObjective-Cのオブジェクトとして扱われないので注意。

importを忘れずに

以前も書いた気がしますがLLDBでbreakした環境はそのままだとFoundation.frameworkすらまともに使えない(シンボルを認識しない)状態なので、importを忘れずにしましょう。
e -l swift -- import UIKit
e -l swift -- import MyApp
e -l swift -- import MyAppLib
という具合でしょうか。他必要なものがあればその都度。注意点として自分のアプリ自体に含まれるシンボル、例えばAppDelegateだとかViewControllerのたぐいだとかもimportしないと触れないので忘れないように。

unsafeBitCast(_:to:)を使ってポインタからオブジェクトを取得する

これも以前書いた気がしますが、Swift 3でまたシグネチャが変わったらしいので念のため。
e -l swift -- let $obj = unsafeBitCast(0x00000000, to: MyClass.self)
e -l swift -- print($obj)
とかやるといい具合になります。

エイリアスを作ろう

毎回e -l swift -- とかタイプするの面倒ですし、poがe -O -- のエイリアスなのと同様に、eswiftとかpswiftとかそういうエイリアスを作ってしまえばいいと思います。~/.lldbinitの中に、
command alias eswift e -l swift -- 
とか書いておけば良い気がします。

2017年3月7日火曜日

NSAttributedString に lineSpace を付与したとき、1行であるにも関わらず lineSpace が下端に付与されて困っている人はこれを読んだら直ります



突然ですが皆さん、NSAttributedStringには2017/03/07付のiOS 10.3現在でも致命的にバグっている箇所があります。

症状:

  • NSAttributedStringにNSParagraphStyleAttributeNameを利用してNSParagraphStyle経由でlineSpaceが付与されている。
  • NSAttributedStringに複数の「区間」が存在する。例えばNSAttributedString全体が単一のAttributeによって構成されているときはこの問題は発生しません。2つ以上の異なるAttributeの区間が必要です。
  • NSAttributedStringを描画するときに、横幅及び文字列の長さの都合で、1行で描画される。複数行になるときにはこの問題は発生しません。
  • NSAttributedStringにNSBackgroundColorAttributeNameが付与されていない。または、NSBackgroundColorAttributeNameとNSKerningAttributeNameの両方が付与されている。

上記の条件を全て満たすとき、
本来、1行の文字列はレンダリングしたときに下端にlineSpaceが付与されてはいけませんが、この条件が満たされていると下端にlineSpaceが付与されてしまいます。先頭の画像の左上のケースがこの問題に相当します。

具体的な問題としては、
  • 問題が発生するNSAttributedStringを使用したUILabel, UITextField, UITextViewの描画内容が思いっきり上にずれます(下に無駄なスペースが発生するので)
  • NSAttributedStringの地点で壊れているので、Core Textを利用したり、boundingRect(with size: CGSize, options: NSStringDrawingOptions = [], context: NSStringDrawingContext?) -> CGRectを使っても一切回避できません

解決策: 

この問題に対する正しいワークアラウンドの方法は一つしかありません。

NSAttributedStringの全Attribute区間にNSBackgroundColorAttributeNameを付与し、かつどの区間にもNSKerningAttributeNameを付与しない。

背景色が不要の場合はUIColor.clearでも付与してごまかしてください。
以上です。よろしくお願いします。

参考:

2016年12月13日火曜日

Instruments を使っていてシンボルが見えないときの対処法

Instruments を久しぶりに起動して Time Profiler を使おうとしたら、びっくり仰天シンボルが全部見えない状態になっているじゃあありませんか。 Swift化したのがあかんのか!?と思って調べてみました。

結論から言うと、先日ビルドの軽量化のためにDebugビルド時にdSYMの出力を止めていたのが原因でした(´・_・`)
http://stackoverflow.com/questions/36882788/how-to-create-dsym-file-in-xcode-7-0
Debug Information FormatをDWARF with dSYM Fileに戻して、dSYMの出力を再開したら問題なくシンボルが出力されるようになりました。なんじゃそりゃ。

それでもだめな場合はInstruments側のFile -> Symbols... を選択すると、直接モジュールのdSYMを指定する事が可能になります。覚えておくと便利かもです。
http://stackoverflow.com/questions/33683690/apple-instruments-has-mangled-symbols-and-greyed-out-symbol-names-when-profiling

2016年12月1日木曜日

pyspa Advent Calendar 2016 1日目: 今年の話

去年に引き続きまして pyspa Advent Calendar 2016 一番槍担当の斧さんakisuteです。よろしくお願いします。

去年はゲームの話をしましたが、今年は人生に一度しかやらなさそうなイベントを大量に発生させましたので、主に今年何をやっていたのかについてご報告させていただきたいと思います。

1. 家を買いました

ずっとワンルームのアパートにしか住んだことがない男だったのですが、30過ぎたので人生一度きりだし家でも買ってみるかと思い、半年ほど探して思い切ってタワーマンションの部屋を買ってしまいました。

正直ちょっと前まで家を買うとか単に負債を抱えるだけの大アホだと思っていたのですが、実際に買ってみると案外悪くなかったです。売ればいいとか資産価値があるとか賃貸より買ったほうが割安とかそういうことはさんざん世間様がおっしゃってるかと思いますので、ここではあんまり世の中では言われていない気づきを書いてみようかと思います。

買うと(当たり前だけど)自分のものになる

賃貸で引っ越しするのとはまるで違う感覚です。まず引き渡しですが鍵と権利書をもらうだけです。クリーニングすらされてません。売主は貸主様じゃないので当たり前です。そこでハウスクリーニングを自分で呼んで掃除してもらい、あとは業者を呼んで鍵を付け替えてもらったりします。なんというか、自分のものなんだな、今まで家を借りてた相手の家主さんというのはこういうことをやってたのかな、という感じがします。

当然(マンションの管理規約の範囲内で)部屋は自分のものなので、壁に穴を開けようがぶち破ろうが工事してみようが全て自由・自己責任となります。逆に老朽化した設備を保守点検するのも自分の責任となります。とにかくなんというか、守るものができたなぁという感じを大いに受けます。

売買は意外と大雑把

私の場合売主さんが全ての鍵を渡すのを忘れてましたというのが後から発覚して大騒動になりました。最終的には無事全部私の手元に来たのですが、大手の不動産仲介屋というのも案外雑なもんだなという気持ちになりました。

あと管理費・修繕積立金の将来の計画(当然年度が上がるごとに高くなる、大雑把に言って5年おきに値上がりで、30年で2倍に上昇すると見積もればだいたいOK)ですとか、固定資産税の額ですとか(これは実際に買うまで見積もりもなかなかできない、年間20万円ぐらいすることもある)、家が広くなるので水道光熱費が上がるとか、そういう諸費用は契約直前までなかなか教えてくれないので注意した方がいいです。大体不動産屋も銀行もローンと最初の管理費・修繕積立金ぐらいまでしか計算に入れずに大丈夫ですよとか言って勧めてくるので目に唾を付けて聞いてください。私の場合はお願いした不動産屋さんが知り合いのツテで、大変親切に説明していただいたお陰で難を逃れました。

買うと銀行が身近になる

家を買わない限り銀行なんてただの財布の延長程度の扱いでしかない、という感覚だと思うのですが(特に私と同年代以下の方は)、家を買うとなると銀行が本格的にお客さんというかパートナーというか商売相手になるというか・・・とにかく全く別の相手になります。銀行の担当の人とは仲良くしましょう。
それから自分の口座から頭金として3桁万円の現金札束を引き出したり、4桁万円の融資が飛び込んできたりするのが目の前で見られてちょっと社長にでもになった気持ちが味わえます。でもできればあんまり味わいたくないです(´・_・`)

買うと強制的に勉強せざるを得ない状態になる

やれ保険があるだのいざとなれば売ればいいだの言われますが、借金は借金です。返せなければ死ぬしかありません。今は金利も安いですが将来はわかりませんし、何より管理費・修繕積立金・自宅設備の更新など、将来必要になるお金は増える一方で減ることは絶対にないと悲観的に考えたほうがよいぐらいです。そして我々の給料はほっといても絶対に上がりません。退職金も年金もありません。

よく家を買うと保守的になるだの守りに入るだのというネガティブな考えを持ちそうになりますが、そんなことはありません。むしろこの激動の時代において、守りに入ったら即座に借金が返せなくなって死にます。死にたくなければ勉強し続け変化し続け攻め続けるしかありません。最大の防御は攻撃です。

そういった理由で最近はゲームをする時間もめっきり減って、代わりに株式投資だの投資信託だのの勉強をしてみたりとか、経済のニュースを見て将来の住宅ローンの金利がどうなるか調べてみたりだとか、仕事もiOSアプリばっかりいじってないでDockerだのansibleだのに手を染めてみたりだとか、筋トレする時間を毎日取って体調を維持してみたりだとか、そういうおっさんっぽい規則正しい生活を強制的に余儀なくされております。

家を買うまではそれまでの努力の成果に甘えてダラダラ過ごせてしまっていたので、家を買って強制的に借金を持って自分を追い込んだのは大正解だったと思っています。

買うと視野が広くなる

生活が強制的に変わるので当然といえば当然なのですが、その他家が広くなるので今まで置けなかった家具が置けるようになったり、筋トレ器具を置けるようになったり、最新のIoTっぽいアイテムを買って試してみたり、カーシェアリングサービスを使ってみたりと、試せるものの範囲が広がります。これは今まで生まれてきてプログラマーしかやったことがない私には大変刺激的です。特にルンバや食洗機には本当に感動していて、私が作ってきたiPhoneのアプリなんてこのルンバの1/100も人の役に立たないと反省することしきりです。

近所付き合いの話

よくタワーマンションを買うと上下格差が〜とか近所付き合いが〜とか言われますが、今私が住んでいるところでは特に上下格差が〜みたいなものは一切見られません。その代わりと言っては何ですが管理組合の人が良くも悪くも恐ろしく有能で厳しい人で、とにかく「マンションの資産価値を守る」という点において凄まじいです。引っ越しの日に私が使った業者の養生がショボかったのを見つけて廊下が割れたらどうするつもりだとものすごい勢いで業者の人に怒鳴ってました。後で菓子折り持って謝りに行きました(´・_・`)

逆に防災訓練を定期的に開催したり、子供向けのハロウィンパーティを開催したり、設備に不備があったらゼネコン建設会社や販売会社のアフターサービス担当に殴り込みに行って無償修理させるなどの活躍をしているようで、敵に回したら怖いけど味方にしたら頼りになるなぁと思って見てます。

近所付き合いに関してはやはり多少はあって、ちゃんと出会った人には挨拶して、お子さんには愛嬌よくして、お隣さんには菓子折り持ってよろしくお願いしますと挨拶に行って、ぐらいは当然必要になります。ですが慣れればなかなか楽しいものです\(^o^)/

2. 婚活をしました

30過ぎたのでいっちょ結婚ぐらいした方がいいだろと思い、ツヴァイというところに申し込んで婚活というやつをしてみましたのでご報告いたします。興味のある方は参考にしていただければと思います。

金の話

大体1年間で30万円払えばOKです。そうすると年間72人だか48人は紹介してもらえることがシステム上保証されます。私はと言うと大体300人弱ぐらい紹介を受けることができました。
なお2年目以降はまた30万円が必要になりますので、短期決戦をおすすめします。なお後述しますがどっちにしろ2年以降続けることはあまりないと思います。

ツヴァイの話

個人的にイオンが好きなのでツヴァイにしたのですが、どうやら婚活業界的には以下のようなヒエラルキーが構成されているようです。

無料アプリ
↓
Omiai, Match, ゼクシィなどの有料アプリ(1万円〜)
↓
楽天オーネット(15万円〜)
↓
ツヴァイ(30万円〜)
↓
パートナーエージェント(40万円〜)

大変身も蓋もない暴言を申しますと、上のものほど婚活とは名ばかりの出会い系アプリで、下の方になるほど今度はアラフォーの行き遅れが滞留する阿鼻叫喚地獄となっております。このことは各社の資料を取り寄せてみて平均会員年齢などを調査すれば分かるかと思います。ツヴァイはだいたい35~40歳、パートナーエージェントになると40〜50歳がボリュームゾーンとなっていました。より上位のサービスで駄目だった人が下に流れてくるという構造があるのかもしれません。

大変な暴言を申し上げてしまいましたが、やはり高い額を払うだけあって、下のサービスになればなるほど入会審査も厳格で(市町村が発行する独身証明書や2年分の源泉徴収票、大学卒業証書を提出する必要があります)、きちんとした担当の方がついてくださってアドバイスを受けたり、相手の女性の方も本気度が高いです。実際に1年間やってみてツヴァイは本当に大満足でしたのでおすすめしておきます。

婚活の話

皆さんおそらく「本当に出会えるのか?」とか「居たとしてもアラフォーの嫁ぎ遅れとサクラばかりなのでは?」とか「自分の趣味に合う人なんているわけがない」とか色々不安があるかと思いますが、断言します。そんなことは一切ありません。普通に魅力的な方ばっかりです。

私のような海外のPCゲームばっかりやってて普通の女の子の話題なんぞ何一つわからないおっさんですら、1年間で300人近く紹介してもらえまして、1/5以上の方には普通にお付き合いの連絡を申し込み、連絡先を交換して実際にお会いしたのが1/10程度で、最終的に趣味がバッチリ合う理想的な女性の方と付き合う事ができました。私自身ビックリです。年間30万円出せて本気で結婚したい方は即座に試す価値があると断言します。

あとはもう一つ気になる、どういう男が人気があるか、ですが、周りの様子を見てみたところ、汚くなくて臭くなくて笑顔が気持ち悪くなければ見た目に関してはどんな人でも全く何の心配もありません。ただし汚いのと臭いのと笑顔が気持ち悪いのだけは即座にアウトで門前払いだったのでそこだけは徹底してください。あとやっぱり身長が高くて体格が良くて声が低い人のほうが(誤差の範疇程度ですが)人気があった気がします。

職業はやはり医者と公務員から順番に売り切れます。付き合ってた彼女もおっしゃってましたが、女性は驚くほど現実を見ます。ただ当たり前ですが医者と公務員じゃないと駄目ということは一切ないのでご安心ください。

むしろそんなことより大事なのは相手を思いやる気持ちをいついかなる瞬間も忘れないことだなぁと思う次第です。

結果の話

そして皆さん気になる結果の方はですが、四ヶ月ほど付き合った彼女に私の不徳のなすところから見事にフラれまして、私が伴侶を持つに値しない人徳のない男であるということが完全に証明された形となりました(´・_・`)
これに懲りて来年以降は変な欲を持たずに良いカルマを積んでいきたいと思います(´・_・`)

3. 焼肉をおごりました





シャトーブリアン、大変美味しゅうございました。

以上、よろしくお願いします。
明日の担当は「お前、誰よ」で一世を風靡したイアン・ルイスさんです。

2016年11月17日木曜日

UITouchGestureRecognizer をやめて UILongPressGestureRecognizer や UIButton を使ってみる

適当なUIViewにUITouchGestureRecognizerを貼ってタッチアクション可能にするという実装を行うことがありますが、UITouchGestureRecognizerはUIButtonと違ってタッチして離すタイミングではなくタッチした瞬間に反応してしまうためユーザビリティ的に望ましくない場合が結構あります。

そんなときはUILongPressGestureRecognizerを代わりに使ってみましょうと言う話です。
http://stackoverflow.com/questions/12830547/how-to-implement-touch-up-inside-in-touchesbegan-touchesended

UILongPressGestureRecognizerというと、長押しして何かコンテキストメニューを表示する際に使うイメージが強いものですが、minimumPressDurationを0.01など十分に短く取ると、事実上タップとほとんど変わらないユーザビリティを得ることができます。さらにUITouchGestureRecognizerとは異なりタッチを認識した後に離すまでの間のイベントをUILongPressGestureRecognizer.stateプロパティ経由で監視することができます。その為例えば
  • タッチされている間は見た目を変える
  • タッチを話したときにアクションを実行する
といった味付けが簡単にできます。

しかしもっと簡単に透明なUIButtonを作って目的のUIViewの上に重ね、そちらにタッチをハンドリングさせたほうがより綺麗で簡単にボタンらしい挙動を再現できたので別にUILongPressGestureRecognizerを使う必要はなかったかもしれません(´・_・`) まぁこういう使い方もあるんだよという参考になれば幸いです。

ビルドが遅いのをどうにかこうにかやりくりする方法

すでに皆様痛感されていると思いますが、Swiftはビルドがとんでもなく遅いです。正確に言うと、Swiftのビルドが遅いのに加え、clang moduleのビルドが遅いため、frameworkの数が増えてくるとビルドにかかる時間がどんどん破滅的なことになってきます。

Swift & dynamic framework導入前はアーカイブ込みで10分以下だったビルド時間が、現在は20分を超えてしまっており、更に今後もSwiftコードの増加ととframeworkの増加に従ってビルド時間が伸びることが予想されます。そこでなんとかしてこの時間を短縮しようと思って調べてみました。

そもそもビルド時間が伸びる原因


  • Swiftのビルドが遅い、特に型推論が遅い
  • frameworkのビルドが遅い
  • Xcode上でcleanを実行するとプロジェクト内でビルドしているのframeworkが全てビルドし直しになり分割ビルドの恩恵が得られない

それぞれ詳しく見ていきます。

Swiftのビルドが遅い、特に型推論が遅い

以下の記事が大変参考になります。
https://thatthinginswift.com/debug-long-compile-times-swift/
Swift 3.0になって随分と高速化したため、型推論のせいで異常に時間がかかるという自体は減りましたが、それでもObjective-Cのコードと比べると10倍以上時間がかかることが多々あります。型を明示すれば高速化に寄与できますが、そうするとSwiftのせっかくの旨味が減るため悩ましいところです。

frameworkのビルドが遅い

dynamic framework = clang moduleのビルドはコードのコンパイル以上の作業を多分に含むため、分割すれば分割するほどそのオーバーヘッドが増えて無駄に時間がかかることになります。

Xcode上でcleanを実行するとプロジェクト内でビルドしているのframeworkが全てビルドし直しになり分割ビルドの恩恵が得られない

個人的に最悪なのがこれだと思います・・・せめて特定のコードに対してのみcleanできれば良いのですが、Xcodeで各種トラブルを避けるためにはcleanは必須、しかしながらcleanを実行すると全てのframeworkがビルドし直しとなって分割ビルドの恩恵が得られません。

特にCocoaPodsを使っていると全てのpod管理下のframeworkが再ビルドとなるため大変効率が悪いです。

これらを踏まえて対処法を考えてみました。と言っても大したことは出来ないのですが、

  • 定期的にビルドタイムをチェックする環境を用意する
  • CocoaPodsをやめてcarthageを使用する
  • microframeworkをやめる、どうしてもmicroframeworkを行うのであればcarthageと併用する

ビルドタイムのチェック方法は https://thatthinginswift.com/debug-long-compile-times-swift/ に記載がありますが、Other Swift Flags-Xfrontend -debug-time-function-bodiesを追加してビルドし、ビルドレポートを見ればOKです。結果はpbpaste | egrep '\.[0-9]ms' | sort -t "." -k 1 -n | tail -10などで整形すると見やすくなります。100msを超えている関数があれば手を打つ必要があるかもしれません。

CocoaPodsですが、手元で全てのframeworkをビルドしようとするためcleanした際の無駄が多いです。carthageを使えば最初にビルドを一回行って後はそれを参照して使いまわすだけ、という風にできます。ただしcarthageは最初のビルドがやたら長く、CocoaPodsを使った場合より5倍以上遅い?と言う問題があるのでその点は注意が必要です。

microframeworkを辞める論についてはおそらくいちばん賛否両論議論があるところかと思いますが、少なくとも殆どの場合、開発中はコードを使いまわす回数と時間よりXcodeをcleanしてビルドし直す回数と時間のほうが支配的であると信じています。Xcodeがちゃんとしてさえいればベストプラクティスに従ったほうが良いのでしょうが、そのようなことは伝統的にないので、ベストプラクティスにこだわらずビルドを高速化するために一つのframeworkにまとめるのも悪くはないのではと思っています。
ただしcarthageと併用できるのであれば、一度ビルドしたmicroframeworkを使いまわすメリットが享受できるので良いかもしれません。その場合、microframework側はコードベースがある程度安定している必要があります。さもなければ何度も何度もcarthage updateを走らせ直して結局時間がかかることになります。

結論

いろいろ考えてみましたが、頑張ってビルド高速化するより、新しいビルドマシンを増設したほうが結果的に良いかと思います\(^o^)/

2016/12/01 追記

Qiitaにより詳細で素晴らしいまとめがあったのでそっちを見ておいたほうが良い気がします(´・_・`)

全体的に見て型キャストとかワンライナー(とそれを誘発する演算子等)による途中の型推論が劇的に遅い原因のようですね。まぁ来年ぐらいまでにはclangが賢くなって直ってるのではないでしょうか・・・多分・・・

2016年11月14日月曜日

UNNotificationAttachment.init(identifier: String, url: URL, options: [AnyHashable : Any]? = nil)に拡張子のないURLを渡す際の注意

iOS 10で導入されたUserNotifications.frameworkは大変便利ですが、落とし穴が早速幾つか見つかっておりますのでご紹介いたします。
参考: iOS 10で画像つきのNotificationを配信する - Qiita

表題にありますUNNotificationAttachment.init(identifier: String, url: URL, options: [AnyHashable : Any]? = nil)ですが、第二引数のurlに画像や動画などのメディアのURLを渡してUNNotificationContentに付与すると言う使い方をするのが一般的かと思います。

このときどうやらUserNotifications.frameworkはurlのpathExtension、要するに拡張子の情報を利用してメディアの種類を判別しているようで、以下のような状況が発生すると実機でのみUNNotificationAttachment.initの実行が失敗してnilが返却されます。

  • 第二引数のurlに拡張子が付与されておらず、かつoptionsに[UNNotificationAttachmentOptionsTypeHintKey: "<当該URLのメディアの適切なMIME Typeを表す文字列>"]が指定されていない場合。
  • または間違った拡張子やMIME Typeが指定されている場合。

なおシミュレータでは問題ありません。いきなり実機に持っていって困るということが有るかと思いますので十分ご注意ください。

対処法は2つあります。

  1. URLに適切な拡張子を付与する
  2. optionsに[UNNotificationAttachmentOptionsTypeHintKey: "<当該URLのメディアの適切なMIME Typeを表す文字列>"]を付与する

何れにせよURLが指し示すメディアの種類が正確にわかっていないと問題になるため、その点は注意が必要です。

2016年10月4日火曜日

Q. ATS を Debug ビルドでだけ無効にしたいのですが...

A. こちらの内容に従えば一発です。

要するにBuild Phaseにビルドスクリプトを追加してそこでPlistBuddyを使って値を書き換えましょうと言う作戦です。

もうちょっとマシな解説

普段、Info.plistの設定内容をConfiguration毎に書き換えたい場合は、たいていよくやるのがxcconfigファイルを以下のように用意して、
APP_BUNDLE_NAME = MyApp_Dev
でもってInfo.plistに対して
Bundle Display Name = ${APP_BUNDLE_NAME}
こんな風に書いておけばビルド時に自動的にXcodeが環境変数による置換処理を行ってくれる、というのを使うのですが、まいったことにATSの設定を司るNSAllowArbitraryLoadsはString値ではなくBool値でして、このInfo.plistに対するXcodeの環境変数置換処理はString値(と、確かNumber値)にしか使えないというオチがあります。仕方がないので最初に説明した方法が最善になるかと思います。Info.plistファイルをConfiguration毎に用意する方法もありますが、大概そっちのほうが設定のメンテナンスが面倒になるのでオススメしません。

2016年10月3日月曜日

ATS の AVFoundationに対する 例外条項の注意点

2016/10/03段階でのATSを有効にする際の覚書です。基本的には全てTLS 1.2以上をサポートするhttpsにしてしまえばOKですが、例外条項としてAVFoundationのStreamについてはhttpでもよいと例外条項が定められています。

https://developer.apple.com/videos/play/wwdc2016/706/?time=324

これについては設定不要で自動的に適用されるようにWWDCのビデオ上では見えますが、実は落とし穴があり、この例外条項はiOS 10以降でのみしか適用されません。iOS 9については実はAVFoundationのStreamについてATSが完全に適用されているようで、httpの動画をStreamingしようとしても失敗してしまうので注意が必要です。

2016/10/03 16:45 追記
こちらXcode 7.3.1でビルドしたバイナリにて調査した内容ですので、SDKのバージョンが異なるXcode 8.0以降でビルドしたバイナリであればiOS 9.x系でもAVFoundationのStreamについてATSの例外条項が適用されてhttpで通信できるようになるかもしれません。ただ紹介した参照元のWWDCのビデオを見ても分かる通り、基本は例外を考えず全てhttps化するのが良いとされているため、ビデオストリームについても可能であれば全てhttps化するほうがトラブルがないかと思います。

2016年9月11日日曜日

Parse.com が潰れたので新しい mBaaS を探す旅に出た

定番のmBaaSとして人気だったParse.comが終わってしまうことが確定してしまった2016/09/11現在、次はどのようなmBaaSを選ぶのが良いのか調べてみました。なおどのプロダクトも全然試せていないので実戦での詳細な評価についてはできかねます。むしろこれを読んだ皆さんに果敢にトライしていただきたい!\(^o^)/

Niftyクラウド mobile backend


http://mb.cloud.nifty.com

  • 国内だとここ
  • ちょっと前にひとりぼっち惑星がここ使ってるといって有名になった
  • 正直機能的にはダメだと思ってる
    • その話題になったブログ記事の内容が、データストアにトランザクションに相当する処理を新機能として導入しました、とかDBキーによる高速ルックアップを実装しました、とかそういう次元の、あまりにも最初から存在して当然と思われる機能を新規実装として誇っているような内容だったため
    • 逆に言えば今後もこの調子で継続的に改善してくれるかも
  • Niftyが社命賭けてるっぽいしそう簡単には潰さないと信じてる

Google Firebase


https://firebase.google.com

  • なんといってもGoogle様ブランドがある
  • Analyticsが非常に素晴らしい、GAをリプレイスして、Analyticsのためだけに導入する価値がある
  • 一方でiOS/Android/Webプラットフォーム全てで同一の概念を利用できるように、各プラットフォーム独自の箇所をラップして激しく潰しているように見える
    • 特にFCMと呼ばれるPush通知に相当する箇所についてはAPNSが元の影も形も見えないぐらい激しくラップされていて操作できないため、例えば今の時期だとiOS 10向けのUserNotifications.frameworkに対応させるのが実質不可能とかそういうデメリットがある
    • iOS側が割を食いそうな不安はある
    • とはいえ調べてみたところiOS 9からのUniversal LinkはApple仕様に従ってきちんと対応するようになっていて、Firebase側は全く無視する気はなく、対応が遅れるのを許容できるのであれば大丈夫そう
  • Googleだし気合入れてるみたいだしそう簡単には潰れない・・・はず・・・
    • とはいえ最近のGoogleは不採算プロダクトを平然と潰すので油断ならない

backendless


https://backendless.com

  • パッと見一番Parse.comっぽい
  • トップページからして「Migrating from Parse? Welcome to Backendless!」だし
  • 一番Parseっぽくて下手にラップするようなことをしておらず、かつ必要な物は全部あるので最も個人的には好感
    • Firebaseみたいに激しくラップされすぎる構造は、例えるならRuby on RailsやPython Djangoのような感覚があってどうも個人的には好きになれない
  • ただしParseの二の舞いのように潰れる可能性もまた否定出来ない


まとめ

どこも潰れるリスクがワンチャンありそうですね(´・_・`)
ご利用はご計画的に\(^o^)/

iOSのフォントのお話

最近フォント周りについて深く掘り下げる機会がありましたので、その際のメモを残しておこうと思います。かなり読む人置いてけぼりな中身になってますが、フォントを詳しく触り始めるとなるほどーとためになる(と思う)のでどうかご了承ください(´・_・`)

UIFontのプロパティについて

UIFontにはフォントに関する数値を表すプロパティが存在します。いろいろありますが、もっとも重要なのは以下に列挙するプロパティです。

  • pointSize
  • lineHeight
  • ascender
  • descender
  • leading
  • capHeight

以下の画像を見ると非常にわかりやすいかと思います。


参照元: https://developer.apple.com/library/ios/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/CustomTextProcessing/CustomTextProcessing.html および http://stackoverflow.com/a/35922853

これを踏まえて各プロパティを解説すると以下のとおりになります。
  • pointSize
    • フォントのサイズを表します。
    • ほとんどすべてのケースでpointSize = ascender + descenderとなり、すなわちpointSizeは上端と下端に空白を含まない純粋なフォントの高さだけを表す数値であると言っても良いと思います。
      • 上下に空白を作らない高さでフォントを描画したい場合はこの高さを使うと良い、と言い切って間違いないと思います、ごく一部の変なフォント以外。
  • lineHeight
    • フォントの上部末端(上端空白が存在する場合はそれも)から下部末端(line gapも存在するなら含む)までの完全な高さを表します。
    • 飾り文字などではないまっとうなフォントであれば、必ずこのlineHeightの中に文字の高さが収まるようになっています。
      • 一部飾り文字のあるフォントについてはfont leadingを使って下部末端のline gapを表現していることがあります。
    • pointSizeとの最も大きな違いは、上端と下端にline gapに相当する空白を持っています。特にシステムフォントで顕著にそれが見られます。詳しくは後述。
    • 実際にフォントが画面上にレンダリングされるときはこのlineHeightがすべての基準になっているケースが多いです。
  • ascender
    • 全てのアルファベットの文字の最大高さを表します。
    • この高さまで描画されるのはごく一部の文字だけで、殆どの文字はcapHeightまでの高さしか使いません。詳細はこの後のcapHeightを参照してください。
  • descender
    • アルファベットのBaselineより下に描画される文字、例えばj p q yなどの下付き部分の高さを表します。
    • 日本語の文字でこの領域に描画される文字は私が知っている限りでは存在しません。
    • UIFontが返す値は常にマイナスになるので気をつけてください。
    • CTFontが返す値は常にプラスです。
      • めんどくさいので常にfabs()を通すといいと思います。
  • leading
    • Baselineからフォントの下部末端(line gapを含む)までの高さを表します。
    • 上記の図ですとline gapの部分だけがleadingとなっていますがヒラギノフォントで試した限りは誤りです。Baselineから下全てが対象になっているようです。
    • lineHeightに含まれている下部末端のline gapとは「全く別の値が付与されていることがあります」。
      • 例えば飾り文字のあるフォントについて、lineHeightよりもフォントの描画が大きくなるぐらい下に飾りが伸びていたとしても、leadingの値はそこまで考慮して付与してあるので、usesFontLeadingのオプションを付与すると下の行にはみでなくなる、みたいなケースが存在します。
  • capHeight
    • アルファベットの大文字の最大高さを表します。
    • 日本語の文字は(特に漢字は)基本的にすべてcapHeightの高さに描画されます。
    • capHeightより上に描画されるのは、例えばÅ(Aウムラウト)のように修飾子が付いているアルファベットや、日本語だと濁点および【】隅付き括弧のような一部の文字が、capHeightよりも上に描画されます。
特に覚えておきたいのがleadingというプロパティの働きです。UIKit, TextKit, Core TextにusesFontLeadingというような名前のオプションが存在するケースが多々ありますが(例えばNSAttributedString.boundingRect(with:options:context:)のoptionsとかですね)、これは何かというと、
usesFontLeadingが指定されている場合、lineHeightの計算の際に常にUIFont.leadingの値が使用される。従って、もしattributed stringにparagraph styleを付与してlineSpacingに0を付与していたとしても、フォントにleadingの値が付与されていたら、それ以上に行の高さが低くならず必ず行間が開いてしまう。
というオプションになります。paragraph styleでlineSpacingやparagraphSpacingを0に指定しているにもかかわらずどうしても行間がゼロにならないとか、指定するより広がってしまうみたいな状況になった場合は、このfont leadingを疑ってみてください。

ちなみにiOS 8以上については以下のとおりです。

  • UILabelは常にfont leadingを使用しません。
  • UITextFieldは未調査ですがおそらく常にfont leadingを使用しません。
  • UITextViewはデフォルトfont leadingを使用しますが、UITextView.layoutManager.usesFontLeadingの値をfalseにすることで回避可能です。
  • その他のラベルパーツ(UIButton.titleLabelなど)については完全には調査できていませんが、UIButton.titleLabelに関してのみいうとiOS 9の地点ではfont leadingが常に「使用されてしまいます」。これはおそらくiOS 6以前のUILabelの挙動がそのまま残っているためではないかと思われます。同じUILabelクラスですが中身と挙動がUILabelと異なるため気をつけてください。このようなケースは他にもあると思われます。

iOSビルトインフォントの問題点

幾つかのiOSビルトインフォントについては、先述のUIFontのプロパティの値に疑問がある数値が設定されているケースが有るようです。具体的にご紹介します。

1. ヒラギノフォントの場合

ヒラギノフォント、すなわちヒラギノ角ゴシックW3/W6、それからヒラギノ明朝W3/W6については以下の様なフォントの設定の特徴があります。
  • leadingの値がきちっと付与されています。
  • 常にpointSize == lineHeightになります。
  • おそらくバグだと思うのですが、descenderの値が本来必要な量の半分しか設定されていません。そのためヒラギノフォントを明示的に付与したラベルにg, j, y, qなどの下付き部分があるアルファベット文字を描画すると下が途中で千切れてしまいます。
    • 対処法としてはfont leadingを使用するか、または自力でdescender分だけラベルの高さを高くするか、明示的に指定してヒラギノフォントなんて使うのをやめてシステムフォントを使用してください。

2. システムフォントの場合

次にシステムフォントはどうなっているのかというと、以下の様な特徴があります。
  • 常にleadingの値がゼロになります。従ってfont leadingを使うモードで描画しても使わないモードで描画しても全く同じ結果になります。
    • iOS 8はHelvetica Neue, iOS 9以上はSan Franciscoが採用されていますが、いずれのケースでも全く同様の設定になっています。
  • pointSizeよりlineHeightが高く、ちょうどその差分の分だけ自動的にline gapとして行間が開くような設定になっています。
    • 要するに簡単にいえば、font leadingを使わないで、かつattributed stringでlineSpacingやparagraphSpacingをゼロに設定したとしても、必ず行間がちょっとだけ空いてしまいます。まずやることはないと思いますが、正確に行間ゼロを作るのは著しく困難です。

UIFontのpointSizeについて

UIFont.pointSizeについて、みなさん普段から何気なしに17とか14とか値を指定されているかと思いますが、このpointSizeに指定する値の単位って気にされていますでしょうか?このフォントのpointSizeとはPostScriptポイントないしDTPポイントと呼ばれるものらしいです。一般的に1PostScriptポイント=1/72インチとされています。

ではこの単位はiOS上で使われている論理座標系のpt単位とは全く違うものなのかというと、どうやら大昔のMacintoshのころからの名残で、macOS/iOS上の論理座標系の1pt=1PostScriptポイントとなるように定められていて、これは現在に至るまで常に維持されているということらしいです。

要するに、UIFontのpointSize=iOS上での1ptと完全に一致します。このことはlineHeightが16のフォントを画面上にレンダリングすると論理単位16pt(2倍Retinaなら32pxになる)ことからも確認ができます。便利ですね。


UIFont.leadingのiOS 8以下での不具合

かいつまんで言うと、UIFont.leadingの値は、iOS 8以下のときに常にUIFont.lineHeightと同じ値を返します。これは"font leading"という単語が使われている文脈や文化圏によって意味が異なり、font leading = line heightとなる文化圏があったので誤用されてしまった、ということらしいです。しかしながら実際のiOSでは先述の通り「font leading = BaselineからフォントのLine gapを含む下端までの距離」と定義されていますから、このような値では困ります。

対処法として、iOS 8以下の場合はCTFontを使ってleadingの値を取得してください。こちらにコードを用意しました。
https://gist.github.com/akisute/3c3d162da73abd784525c9ed7859cda2

なお、iOS 9以上はこの問題は発生しません。iOS 8なんてそろそろサポート切れるんで忘れていいかもしれませんが、念のため。

まとめ

デザイナーさんにフォント周りで行間調整がおかしいぞーとかベースラインがずれてるぞーとか突っ込まれまくっていて必死こいて修正方法を調べている時に、こんな記事を見つけました。

https://www.raizlabs.com/dev/2015/08/advanced-ios-typography/

Stop Saying "No" to Designers.
このサイトには英字フォントをBaselineではなくcapHeightの位置で上揃えにするべく奮闘した一人の漢の話が書かれています。おお、海外にも私と同じ境遇の漢が居たのです。

我々もデザイナーにNOと言わないタイポグラフィを実現させたいですね\(^o^)/

2016年6月30日木曜日

Auto Layout と Manual Layout を混載させるときに役立つ UIView.translatesAutoresizingMaskIntoConstraints プロパティの話

Auto LayoutがiOS 6で導入されてはや4年、未だによく理解していなかった挙動に UIView.translatesAutoresizingMaskIntoConstraints があります。このプロパティは自分がプログラムコード上で生成したviewをAuto Layoutするときにfalseにする必要があるものということで皆様記憶されているかと思うのですが、具体的にこのプロパティは何をやっているのかが個人的に全く謎でした。それが今日一つ謎が解けましたのでここに共有させていただきたいと思います。

UIView.translatesAutoresizingMaskIntoConstraintsの値がtrueのときとfalseのときの違いについて以下に記載します(iOS 8以上で確認しています)。

  • trueのとき
    • 対象のviewのframe、すなわちx, y, width, heightの4つの要素をview.frame, view.bounds および view.center プロパティから直接操作することが可能になります。これはAuto Layoutが導入される以前のiOSの世界と同じ状態です。この挙動をAuto Layoutとマッチさせるため、対象のviewのx, y, width, heightの4要素を指定された値に固定するようなAuto Layout Constraintsが自動的にシステムによってviewに挿入されます。この自動的に挿入されるAuto Layout Constraintsのpriorityは常に1000 (Required)になります。
  • falseのとき
    • 対象のviewのframe、すなわちx, y, width, heightの4つの要素はすべてAuto Layoutエンジンが管理するようになり、view.frame, view.bounds および view.centerの値を直接書き換えても一切無視されるようになります。Auto Layout Constraintsが設定されていない場合、viewのframeはCGRect.zeroになります。

プロパティの名前にAutoresizing Maskとか入っているのでてっきりAutoresizingの仕組みに影響している用に見えますが、実際には全く関係ありません。その証拠にAutoresizingMaskの値をどのように変化させても勝手にAuto Layout Constraintsが挿入されてしまいます。このプロパティはあくまで当該viewのframeを自動的に操作するようなAuto Layout Constraintsを挿入するか否かを決めるフラグとして覚えると良いでしょう。

さてこの挙動を覚えると何が嬉しいかと申しますと、Auto Layoutと非Auto Layoutを混載させるときに非常に役立ちます。こうすることで、特定のviewだけをframe手動操作で設定し、他のviewはAuto Layoutに任せるというような荒業が自由自在に可能になります。

具体例を見てみましょう。例えば以下の様なニュースを表示する画面を作ってみようと思います。



ここでこのnewsを表示するviewのframeは複雑なアニメーションをさせたいなどの理由で外部からマニュアルで設定したいが、viewの中身はauto layoutに任せたいというようなケースがあるかと思います。

というわけで普通にAuto Layoutで作ってみましょう。
private func commonInitialize() {
        self.translatesAutoresizingMaskIntoConstraints = true
        self.backgroundColor = UIColor.white()
        
        self.imageView = UIView()
        self.imageView.translatesAutoresizingMaskIntoConstraints = false
        self.imageView.backgroundColor = UIColor.green()
        self.addSubview(self.imageView)
        
        self.titleLabel = UILabel()
        self.titleLabel.translatesAutoresizingMaskIntoConstraints = false
        self.titleLabel.text = "factorio alpha 0.13 has been released!"
        self.titleLabel.font = UIFont.preferredFont(forTextStyle: UIFontTextStyleTitle1)
        self.titleLabel.numberOfLines = 2
        self.addSubview(self.titleLabel)
        
        self.articleLabel = UILabel()
        self.articleLabel.translatesAutoresizingMaskIntoConstraints = false
        self.articleLabel.text = "In 0.13 we have the new multiplayer matching server and server browser. This will let you find games of people online join your friends and other stuff. Server games are published to the server and clients can browse existing games. The first thing you will notice is the new multiplayer menu. When you click on 'Browse Public games' you will be asked to log in to your factorio account."
        self.articleLabel.font = UIFont.preferredFont(forTextStyle: UIFontTextStyleBody)
        self.articleLabel.numberOfLines = 0
        self.addSubview(self.articleLabel)
        
        let views: [String: AnyObject] = ["imageView": self.imageView,
                                          "titleLabel": self.titleLabel,
                                          "articleLabel": self.articleLabel]
        
        self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[imageView]|", options: [], metrics: nil, views: views))
        self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-10-[titleLabel]-10-|", options: [], metrics: nil, views: views))
        self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-10-[articleLabel]-10-|", options: [], metrics: nil, views: views))
        self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[imageView]-10-[titleLabel]-10-[articleLabel]|", options: [], metrics: nil, views: views))
        self.imageView.addConstraint(NSLayoutConstraint.init(item: self.imageView, attribute: .height, relatedBy: .equal, toItem: self.imageView, attribute: .width, multiplier: 0.66, constant: 0))
    }


しかしながらこのコードはAuto Layout Warningが発生してしまいます。

2016-06-30 23:24:12.050906 AutoLayout[1725:80607] [LayoutConstraints] Unable to simultaneously satisfy constraints.
 Probably at least one of the constraints in the following list is one you don't want. 
 Try this: 
  (1) look at each constraint and try to figure out which you don't expect; 
  (2) find the code that added the unwanted constraint or constraints and fix it. 
 (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "NSAutoresizingMaskLayoutConstraint:0x7fd60ae1db90 h=--& v=--& AutoLayout.Example2View:0x7fd60ad35b70.width ==   (active)",
    "NSLayoutConstraint:0x7fd60ac360a0 H:|-(10)-[UILabel:0x7fd60ad3c6b0'factorio alpha 0.13 has b...']   (active, names: '|':AutoLayout.Example2View:0x7fd60ad35b70 )",
    "NSLayoutConstraint:0x7fd60ac36380 H:[UILabel:0x7fd60ad3c6b0'factorio alpha 0.13 has b...']-(10)-|   (active, names: '|':AutoLayout.Example2View:0x7fd60ad35b70 )"
)

これは先程のUIView.translatesAutoresizingMaskIntoConstraintsについての説明を元にすると以下のように解釈できます。

  1. UIView.translatesAutoresizingMaskIntoConstraintsがtrueに設定されていることにより、このviewにはwidth=frame.size.widthになるようなAuto Layout Constraintsが自動的に設定されている。
  2. このviewにはさらに "H:|[imageView]|"となるようなAuto Layout Constraintsが設定されている。これはimageViewを横幅いっぱいに表示するため。
  3. しかしながらこのような設定を行うと、imageViewが親となるviewの横幅を自分の横幅に合わせて引っ張ろうとするConstraintsが定義されてしまうので、1. で自動的に設定されたConstraintsと衝突してしまう。
  4. 結果としてwarningが発生する。
これを回避してやるにはいくつか方法があります。

  1. "H:|[imageView]-(0@999)-|"のように設定することで、右側ないし下側のpriorityを999に下げる。こうすることによってUIView.translatesAutoresizingMaskIntoConstraintsによって設定されるConstraintsのpriorityが勝つためワーニングは発生しなくなる。
  2. 両側を引っ張るようにvisual formatを使って設定するのをやめて、view.x=imageView.x, view.width=imageView.widthとなるようにConstraintsを付与する。
例えば2. のケースはiOS 9以降であればNSLayoutAnchorを使って簡単に設定ができます。

self.imageView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
self.imageView.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 1.0).isActive = true

これでAuto LayoutとManual Layoutをより自由自在に混載させることが可能になると思います。

2016年6月19日日曜日

ゼロから始めるマインクラフトのサーバー構築の話

突然なのですが、最近いまさらながらマインクラフトに大ハマリしておりまして。きっかけは友人のマルチプレイサーバーでプレイしたことだったのですが、やっぱり皆で遊ぶゲームは面白いなと再確認している次第です。マインクラフトはリリース直後、βが取れたぐらいに遊んで面倒でやめてしまったのですが、近頃はFeed The Beastと呼ばれるオールインワンのランチャーアプリが登場しており、こいつがMODの導入や管理も全て一発でやってくれるため面倒やストレスが一切なく楽しめます。

で、しばらく遊んでいたのですが、友人が他のゲームにハマってしまったためマインクラフトのサーバーを立てなくなってしまいました。そこで私のほうで自分でマインクラフトのサーバーを立ててやろうと思い立ちまして早速今週末チャレンジしてみました。

ちなみに私インフラに関しては大の苦手で、サーバーをゼロから構築するのは初めてだったりします。大昔新卒の頃にSIerの仕事でちょっと触ったりとか、その後の仕事でちょっと触ったりとかした程度。アプリケーションなら分かるんですけど(´・_・`)

前置きはこのぐらいにして早速やってみましょう。

ホスティングサービスを選定

パッと思いつくところだとさくらVPSとAWSが有名ですが、今回は友人のおすすめでConoHaというVPSホスティングサービスを選んでみました。ほとんどさくらVPSと大差ないように見えたのですが、ConoHaのほうがさくらVPSと違い初期費用がかからないらしいです。新しいもの好きですしせっかくなのでConoHaを選択。

サーバーのスペック等は以下のようにしました。CentOS 7.2に3コア2GBメモリで月1750円。マインクラフトのサーバであればこのぐらいの性能で大丈夫なはず。


とりあえずサーバーを起動してsshまで

構築したらすぐにインスタンスが起動します。ConoHaでは自動的に固定Public IPが1インスタンスに割り当てられるようで、この点固定IPを用意したりするのが何かと面倒なAWSに比べて楽でした。管理画面もすっきりまとまっていて使いやすく好印象です。キーペアもその場で同時にすぐ作成してくれて手間がかかりません。

生成したキーペアをローカルの ./ssh 以下に保存して ./ssh/config を適当に設定してログイン。root@を付け忘れてssh接続要求に失敗するみたいなよくあるお約束のミスをしましたが、特に問題なくログインできました。

ログインに成功したのを確認してから手持ちのドメイン (akisute.com) のサブドメインを用意してDNSを設定してやります。疎通して問題なし。

nginxを立てる

サーバーの情報などをウェブに公開したいので、nginxを利用してstatic htmlファイルをホスティングできるようにします。nginxのCentOS 7.2に対するインストール自体はこちらのページに従えばすぐにできます。
http://nginx.org/en/linux_packages.html
後は以下のビギナーズガイドに従って設定を行えば良いのですが、基本的には最初から全て必要そうな設定は用意されているので、/usr/share/nginx/html以下のindex.htmlを適当に編集してやるだけでオッケーです。
http://nginx.org/en/docs/beginners_guide.html

firewalldを設定する

しかしながらnginxを立ち上げただけでは外からアクセスしてもつながりません。これはCentOS(に限らないですが、ほとんどのLinuxのディストリビューションについて)は何らかのファイアウォールがデフォルトで入っているためです。一般的にはiptablesが使用されているのですが、CentOS 7ではiptablesではなくfirewalldというやつが使われているらしいので、そちらを設定することにしました。
http://urashita.com/archives/963
http://www.unix-power.net/centos7/firewalld.html

httpサービスをpublicゾーンの設定に追加するだけで簡単に外からアクセスできるようになります。iptablesより簡単でいいですね。

Javaをインストールする

マインクラフトはJavaで動作するので最新版のJavaが必要です。Javaには大きくOpenJDKとOracle Javaがありますが、個人的にはOracle Javaのほうが速そうなのでそちらを使うことにしました。以下のページで紹介されているとおりにすれば簡単にインストールできるかと思います。
https://www.digitalocean.com/community/tutorials/how-to-install-java-on-centos-and-fedora

minecraft-serverを立てる

いよいよ本題のminecraft-serverです。これについてはFTBのmodpackであればFTBのクライアントからDownload Serverボタンを押すだけでzip形式で最初から用意されたサーバーセットアップ一式がダウンロードできますので、ダウンロードURLをコピーしておいてサーバー側からwgetして取得しunzipするだけで準備完了です。
http://www.feed-the-beast.com/servers

unzipして展開した中身のServerStart.batをServerStart.shにして中身を適当に書き換えてやり(主にJVMの起動オプションからヒープに割り当てる量を調整するなど)実行すれば簡単にサーバが立ち上がります。ただし普通に立ち上げるとシェルを専有されてしまうので、screenなど経由でServerStart.shを実行するようにすればいいでしょう。
http://www.minecraftforum.net/forums/support/server-support/server-administration/1897274-how-to-use-screen-on-your-linux-minecraft-server

あとはサーバーコマンドで自分をopにでも追加しておけば良いと思います。起動したサーバーのコマンドラインから /op 自分の名前 とコマンドを実行すればopを付与できます。その他必要に応じてminecraft-serverの設定ファイルを操作したりgameruleを調整すれば良いと思います。

最後に先ほど設定したfirewalldにminecraftサービスを追加して、外からアクセスできるようにすればおしまいです。minecraftはhttpと違ってデフォルトでサービスが用意されていないので、自分でサービスを追加する必要があります。サービスの追加は/etc/firewalld/servicesにxml設定ファイルを追加すればよいです。サンプル代わりにデフォルトのサービスのxml設定ファイルが/usr/lib/firewalld/services以下に配置されているので適当なものをコピーして改造して使えばよいです。minecraftはtcp/udpの25565がデフォルトポートなのでここを開くように設定してfirewalldを再起動すれば外から繋がるようになるかと思います。

その他の設定

ログローテーションを設定しておかないとディスクがログで溢れて死ぬかと思ったんですが、最初からnginxについてはログローテーションが設定されているため気にしなくてOKです。minecraft-serverについてはどうなるかわかりませんので、必要に応じてlogrotateを設定して対応したほうが良いと思います。
https://genchan.net/server/4907

まとめ

いろいろ調べながらで時間がかかりましたが、3時間ほどでゼロ知識の状態からでもマインクラフトのマルチプレイサーバーを立てることができました。現在のところ無事快適に遊べています\(^o^)/

こういう自分の趣味嗜好のために技術を勉強すると非常に捗るのでオススメですね。みなさんもゲームを楽しみながら技術を勉強して一石二鳥になりませんか\(^o^)/


2016年1月7日木曜日

Asset Catalog を使用しているときは [UIImage imageNamed:] が遅くなることがある

タイトルのとおりですが、本日発見してひどくパフォーマンスに影響が出たため注意喚起を兼ねて共有いたします。

最近のiOSプロジェクトは全て画像をxcassetsすなわちAsset Catalog経由で管理することが多いかと思いますが、このAsset Catalogを使用している際に[UIImage imageNamed:]経由で画像を取得するとiOS 8以前とは異なり大変画像の取得が遅くなることがあるようです。

詳細はこちら。
https://forums.developer.apple.com/thread/17888

実際に私の環境では目に見えて遅くなりました。Instruments経由で計測したところ最大で10倍ほど差が出ているように見えました。iOS 9.1以降は修正されているとのことですが、それでも[UIImage imageNamed:inBundle:compatibleWithTraitCollection:]を使用したほうがパフォーマンスへの悪影響が少ないとの情報があります。実際Instruments上でもTrait Collectionを検索するのに無駄なパワーを使っているように見えたのでパフォーマンスがタイトな箇所では自分で非同期ロードするなりキャッシュするなりして補ってあげたほうが良い気がします。

こちらからは以上です。

2015年12月17日木曜日

Xcode 7.2 の LLDB で Swiftのデバッグをするコツ

現在のXcode 7.2でSwiftを使ったiOSアプリのデバッグをするときのコツみたいなものをまとめました。将来的にはより良くなる可能性はあります。というか良くなってほしいです(´・_・`)

■LLDBはbreakした地点によって挙動が変わる

まずハマりどころがこれですが、現在のLLDBはbreakした地点で実行されていたコードがSwiftのコードかC言語系のコードかによってモードが変わります。
// Objective-C mode
(lldb) po [someObject property]

// Swift mode
(lldb) e someObject.property
Objective-Cモードの時にSwiftっぽい呼び出しをしたり、その逆をしてもまともにLLDBは動作しません。なので現在自分がどちらのモードのLLDBにいるのかを判断するのがキモになります。

ハマりどころとして、例えば実行中におもむろに停止ボタンを押してみるとか、debug viewボタンを押してみると、全部のコードがSwiftで書かれているアプリにも関わらずいきなりLLDBがObjective-Cモードで起動します。これはbreakされるのがシステムのC言語で書かれた領域だからです。

ちなみに現在のモードがどちらかを判断する方法は無いような気がします。気合と慣れで判断してください。オープンソースになりましたし、そのうち改善されると思います\(^o^)/

■SwiftモードのLLDBでprint object (po) したい

SwiftモードのLLDBのpoはかなり微妙な実装になっているので、SwiftモードでLLDBを使う場合にはpoの代わりにexprすなわちeを使うことをオススメいたします。
(lldb) e someObject
参照: http://stackoverflow.com/questions/28016227/when-debugging-swift-code-can-i-get-a-typed-reference-to-an-object-given-just-i

なんかこちらの記事ですとe -O -d run --で実行するといいよって書いてますけど何が違うのかはよくわかってません。個人的には普通のeでだいたい問題なかったです。

■SwiftモードのLLDBでポインタアドレスからオブジェクトを起こしたい

Objective-Cであれば
po 0x123456789012beef
で終わりなんですが、SwiftモードのLLDBはそこまで面倒を見てくれません。そこでunsafeBitCast()関数が便利に使えます。
(lldb) e let $v = unsafeBitCast(0x123456789012beef, MyView.self)
(lldb) e $v
これではかどりますね。

レガシーな Objective-C プロジェクトを Swift なプロジェクトに変換する

ここで言うレガシーなObjective-Cプロジェクトの定義とは
  • iOS 7時代 (Xcode 5) より前に作成されたプロジェクトである
  • Swiftのコードを一行も含んでいない
  • IOS_DEPLOYMENT_TARGETが8.0よりも小さい (7.xをサポートしている)
とします。

こんな由緒正しいiOSのプロジェクトを未だにメンテしている人もなかなかいないのかと思いますが、もしいらっしゃいましたらそんな方のためにSwiftなプロジェクトに変換していく方法をメモしておきます。

■前提条件

まずIOS_DEPLOYMENT_TARGETを8.0以上にしましょう。IOS_DEPLOYMENT_TARGETを8.0以上にすることでdynamic frameworkおよびclang moduleが使えるようになるため、Objective-CとSwiftの間の垣根が非常に低くなります。

■ケース1: 根っこはObjective-Cのまま、Swiftのファイルを追加

普通にSwiftのファイルを追加したらXcodeが上手いことしてくれます。具体的には

clang moduleを有効にしてdyldを使えるようにする設定
CLANG_ENABLE_MODULES = YES;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";

Swift化されるターゲットごとにブリッジングヘッダを作成
SWIFT_OBJC_BRIDGING_HEADER = "myapp/myapp-Bridging-Header.h";

Releaseビルド以外に対して最適化レベル設定
SWIFT_OPTIMIZATION_LEVEL = "-Onone";

が自動的に行われるので後はせいぜいブリッジングヘッダに使ってるObjective-Cのヘッダを書き込む程度ですみます。

■ケース2: 根っこからSwiftにする

「根っこからSwift」とは要するにmain.m(C言語のmain関数)を持たないアプリにしたいということですが、これも実は思ったより簡単にできます。
  • AppDelegate.swiftファイルを作成する
  • 既存のObjective-Cで書かれたAppDelegateを継承て新しくswiftなAppDelegateを作成して、@UIApplicationMainアノテーションを付ける
  • main.mを消す
参考: http://stackoverflow.com/questions/31309249/how-to-convert-objective-c-appdelegate-to-swift

たったのこれだけでプロジェクトの根っこがSwiftな状態になります。簡単ですね。

■さらにSwift化をすすめる

とりあえずAppDelegate.swiftを作ることで根っこはSwiftになるのですが、せっかくだからAppDelegateをまるごとSwiftにしてしまいたいものです。ここでもし、
  • UIWindowをAppDelegate内部でカスタマイズしている
  • UIApplicationをmain.mでカスタマイズしている
などの場合はそのままだとSwiftで対応するのがちょっと面倒です。

まずUIApplicationについてはInfo.plistのNSPrincipalClassを変更することで任意のカスタムクラスに差し替える事が可能です。参考はこちら: http://stackoverflow.com/questions/31642956/how-to-detect-all-touches-in-swift-2

UIWindowについてはMain.storyboardを使わなければ勝手にUIWindowを差し込まれることはなくなるので問題ないのですが、それでは困ると言う場合はawakeFromNibとかを実装してその中で
@UIApplicationMain
class MyAppDelegate: NSObject, UIResponder, UIApplicationDelegate {
  var window: UIWindow?
  override func awakeFromNib() {
    super.awakeFromNib()
    guard let defaultWindow = self.window else {
      fatalError("Something is wrong")
    }
    let window = MySuperDuperWindow()
    window.rootViewController = defaultWindow.rootViewController
    self.window = window
  }
}
とかすれば動くと思います。多分。

2015年12月1日火曜日

pyspa Advent Calendar 2015: 2015 GOTY for Programmers

この記事は pyspa Advent Calendar 2015: 12/1 分の記事です。先鋒を担当いたしますakisuteこと小野と申します。よろしくお願いします。なんか3年前にも先鋒をやったような・・・

さて2015年も終わりに近づいてきたということで、本来はGame of the Year for akisuteというタイトルで私的に今年最高だったゲームについて延々と無駄に語ろうかと思っていたのですけれども、Qiitaでは利用規約には一切明記がありませんがプログラマーに役立たない記事は投稿できないという制約がございまして、大変遺憾ながらタイトルを無理やりプログラマーに役立ちますように変更させていただいた次第であります。

しかしながらご安心ください。今年2015年はプログラマー向けゲームの大豊作当たり年でございます。プログラマーにこれからなりたい人向け、プログラマーに育成したいお子さん向け、ガチのプログラマー勢向けのゲームを多数ご用意させていただきましたので最後までお楽しみいただければと思います。

それではまえがきはこの程度にいたしまして・・・いよいよ各賞の発表です!

■お子さんのプログラマー教育向け賞: CodeSpells


あなたのお子さんを将来プログラマーに育てたいと考えているのであればこのようなゲームはいかがでしょうか?というわけで自分で魔法を自由にプログラミングして使えるゲーム、CodeSpellsがノミネートです。こちらのゲーム自体はかなり昔から発表されていたのですが、Steam Early Accessが始まったのが今年のようなので今年のノミネートとさせていただきました。

ゲーム自体はオープンワールドのゲームで目的等はまだ特になく開発段階という感じですが、このゲームのキモである魔法を自由にプログラミングして行使する点は現在でもバッチリ遊べます。各魔法にはイベント・アクション・それから分岐とループの制御構造が用意されていて、例えば
  • 光の玉を正面に発射する
  • if 光の玉が何かに接触する
  • 自分自身を接触地点にワープ
とか、
  • for 100回ループ
  • 炎をi*3.6度方向に発射
とかいろいろ工夫して魔法が作れるようになっています。大人が遊ぶにはちょっとパズル要素がないとか目的がないとか現段階では問題点が多いのですが、子供が想像力を働かせて遊ぶという当初の目的上であれば自由に地面を隆起させたり石を吹き飛ばしたり津波を起こしたりして遊べるのではないかなと思います!

■プログラマー初心者向け賞: Human Resource Machine


World of GooやLittle Infernoなどで有名なTomorrow Corporationの新作、Human Resource Machineが今年のプログラマー初心者向け賞にノミネートしました!このゲームはどんな命令でもただ言われたとおりに働くただのサラリーマンのおっさんに簡単な命令セットを与えて目的を達成する、つまり
おっさんをプログラミングするゲーム
です!新しいな。

Tomorrow Corporationらしい独特の世界観がただただ言われたとおりに動くおっさんを彩り、一風変わった雰囲気を味わうことができます。この世界観だけでもこのゲームをやる価値があります。

さてこのおっさんを操るプログラミング言語(仮におっさん言語と呼ぶことにする)ですが、一般的なアセンブリ言語の命令セットと同じような感じになっています。またレジスタ兼RAMの代わりとして床のカーペット上の区画を自由に使うことができるようになっていて、ちょっと工夫すればこんなふうにおっさんにバブルソートをさせる事ができます!

おっさん言語のコーディングはビジュアライズされていて、各命令ラベルをドラッグアンドドロップするだけで快適にコーディングが楽しめます。コメントラベルも手書きで好きなように書けるようになっていて、プログラミングをやったことがないカジュアル層の人でも無理なく遊べます。反面ガチ勢の人には簡単すぎるかもしれません。一応命令セット数とステップ数の最適化チャレンジが用意されているので、ガチ勢の人はそちらで楽しんでみてはいかがでしょうか?

■自動化大賞: Factorio


皆様
プログラマーにとって
最も邪悪なことは何でしょうか?

手作業です!

皆様
プログラマーにとって
最も素晴らしいことは何でしょうか?

自動化です!

自動化こそ正義!
自動化こそ未来!
今こそゲームも自動化されるべき!!

ということで今年最も自動化が進んだゲームに送られる自動化大賞には、スーパーマリオメーカーを差し置いてFactorioがノミネートされました!

Factorioについて解説する前に少し前置きをさせてください。
皆様はMinecraftというゲームをご存知でしょうか?
今やスーパーマリオに肩を並べ、Minecraft系という一大ジャンルを作り上げた超有名作だと思います。

しかしながら私このMinecraftというゲームが大嫌いでして。

例えば鉄の武器が欲しいじゃないですか。
なので一生懸命地面を掘って鉄を探して、鉄が見つかったら今度は石炭を探して、持って帰ってきて自分でかまどにぶち込んで鉄にして、今度は鉄を自分で並べて装備を作って、また鉄が足りなくなったら自分で地下に潜って掘ってって繰り返して。

めんどくせえんだよ!!!!!!!
人力手作業で作業するからスケールしねえし!!!!!
自動で掘らせろや!!!!!!!!!

って思っちゃうんですね(´・_・`)

Factorioも基本的にはMinecraftのようなゲームです。
ある宇宙飛行士がとある惑星に不時着してしまって帰れなくなっちゃうんですね。
そこで現地の素材を集めて加工してサバイバルして、最終的にはロケットを作って打ち上げて星から脱出するのが目的のゲームです。
というわけで最初は木を斧で切り倒したり、鉄鉱石をツルハシで掘ったりするんですね。

違うのはここからです。
手作業で鉄を掘るとかめんどくさいじゃないですか。そもそもロケットを作るには膨大な料の鉄が必要だというのに手で掘るとか現実的じゃないですよね。
そこで主人公は鉄を掘るためのツルハシ・・・じゃなくて、なんと自動砕石機を作るんですね。
それから自動で材料を運ぶためのベルトコンベアと、自動で材料を加工してものを作成する機械と、自動で機械に燃料や材料を投下するロボットアームを作るんですね。
あとは砕石機からベルトコンベアで材料を運んで、ロボットアームで機械に投下すれば、自分が寝てても別の所を探検しててもエイリアンと銃撃戦を繰り広げてても勝手に新しい製品がドンドン製造されていくんですね。

なんて賢いんだこの主人公は!!!!!!!!!!

って思っちゃいますよね\(^o^)/

そう、このゲームは
自動化こそが正義
スケールする生産こそが正義
生産効率の最適化のために機械の配置を試行錯誤するのが正義

まさにプログラマーのプログラマーによるプログラマーのためのゲームなのです!

こうして最初は1秒間に1個しかつくれなかった鉄が、工場生産効率の改善とスケールメリットのおかげで最後には1秒に1万個以上生産できるようになります。ヤバい。
こうなってくるともう最適化欲求がドンドン高まってきて、そのうちロケットを打ち上げることが目標だったのが、1秒間に何発ロケットを打ち上げられるかみたいなゲームになってきて、資源を惑星の片っ端から鉄道で運搬し、邪魔するエイリアンを片っ端から銃弾爆薬戦闘ロボットの波で蹂躙し森林を伐採して燃やし・・・どっちが悪役かわからなくなってきましたね(´・_・`)

さらにこのゲームはゲーム内のオブジェクトがすべてコンソールからluaスクリプトで操作ができ、luaスクリプトを書くことで自由にMODをプログラミングすることができます。そのため究極的にはパソコンの前で座って眺めているだけで自動的に主人公が工場を作ってロケットを打ち上げてゲームをクリアするMODをプログラミングすることも理論上可能なわけです!

自動化万歳!!

■GOTY for Programmers: TIS-100


しかしながらそのFactorioを差し置いて、今年のGOTY for ProgrammersはこのTIS-100で決まりです!名前からしてプログラマーっぽいこのゲーム、簡単に説明しますと
  • 主人公(=あなた)の叔父に当たる人物が最近、原因不明の死を遂げた
  • 叔父は生前プログラマーで、遺品整理していたらTIS-100という1980年代製の謎のコンピュータが出てきた
  • TIS-100の中には叔父の生前のメッセージや日記などが残っていた
  • 主人公であるあなたはTIS-100のプログラムを復活させてすべての謎を解き明かしていく
という設定になっております。地味、地味の極地ですね。プログラマー以外眼中にない、まさにプログラマーの・プログラマーによる・プログラマーのためのゲームとしか言いようがありません。

この作品の焦点となるTIS-100コンピュータですが、通常の1980年台のコンピュータとは全く異なるユニークな分散コンピューティングアーキテクチャを採用しています。
  • 各プログラムは横に4つx縦に3つ、最大12個の「ノード」と、複数の「入力ポート」「出力ポート」で構成される
  • 各ノードは最大上下左右4つの「ポート」で結ばれている
  • 各ノードにはアセンブリ言語を用いて命令を記述できる
  • 各ポート間はGo言語のチャンネルのような仕様を用いて入力・出力を行ったり、各ノード間の協調動作を行ったりすることができる。
  • 各ノードの命令はすべて並列で実行される
このアセンブリ言語とモダンな言語の特性を併せ持ったアーキテクチャがTIS-100でのプログラミングの最大の面白さです。うまくコーディングしてノード間を並列させれば実行ステップ数を劇的に減らしたり、複数ノード間でちょっとしたmap-reduceのような作りさえ実現することができます。

しかしながらですね、このTIS-100
異常なほど難しい
んです

開発者Zachtronicsの前作はSpaceChemというゲームでして、こちらのゲームも後半異常な難易度になってくるのですが、本作TIS-100も負けず劣らず難易度が凄まじいことになります。主に難易度が高くなる原因として、TIS-100コンピュータのアーキテクチャ上の絶妙な制約があげられます。例えばこんな感じです:
  • 1ノード内にたったの15行しか書けない。ちなみに空行、ラベル行、およびコメントも1行として数えられます。
  • 1行に確か20文字だか30文字前後しか書けない。ラベル名等は極端に短くする必要がある。
  • 1ノードにレジスタが2つしかなく、そのうち片方は計算レジスタとして使用できずただ値を一時保存するしかできない。さらにこの一時保存レジスタと値をやり取りするだけで追加の1命令が必要。
  • INCL/DECL命令がない。
  • 上記制約が合わさって1ノード内でループ構造を作るのが著しく難しく、汎用のどこでも通用するループ構造を構成するのが難しい。
というわけで普通の手続き的なコードを書くことができず、まさにTIS-100のためのまったく新しいコーディングの仕方を考えていく必要があってとにかく辛いです。実際私は最後までクリア出来ていません(´・_・`)

このただでさえ高い難易度に加え、さらに各問題には最適化チャレンジというものがありまして、使用したノード数・使用した行数・実行ステップ数の3つの点からプログラムの最適化を全世界のプログラマと競うことができるようになっています。おかげで世界中でドハマりするプログラマーが続出。ついにハッカーズマニュアルやらC言語製のシミュレータ、果てはWeb上で動作するTIS-100用コードエディタがまでが登場してしまいました。捗る!

ぜひ我こそはというガチ勢のプログラマの皆様、年末年始のお休みはこのTIS-100の謎を解き明かすのに使ってみてはいかがでしょうか?

それでは皆様、来年もHappy Gaming!

2015年11月29日日曜日

iOS でヒラギノフォントが明示的に指定された時に描画サイズの計算が正しくならない問題を修正する

タイトルからして出落ち感が少々ありますが・・・

iOSのフォントサイズ計算には長年修正されないバグというか仕様がございまして、「ヒラギノフォント(ヒラギノ角ゴシック、ヒラギノ明朝等)」が明示的に[UIFont fontWithName:size:]で指定されたとき、そのフォントを使ったUILabelやUITextViewなどの描画サイズの計算が正しくならない問題があります。iOS 6からiOS 9.1現在に至るまでずっとなので今後も直ることはないと思います。

詳細についてはこちらの記事が詳しいです。
http://qiita.com/yusuga/items/2be8c55ca561bba44702
一番下のリンク先の記事でも同様の問題が訴えられていまして、それぞれ対策が記載されていますので合わせてご参照ください。

でまぁ、対処法としてはいくつかあります。
  • UIControl.contentVerticalAlignmentFillにする
  • sizeThatFitsおよびintrinsicContentSizeの実装を差し替える
  • ヒラギノフォントを明示的に指定するのをやめる。システムフォントを使えばいいじゃない\(^o^)/
  • システムが提供するヒラギノフォントを使うのをやめて、モリサワさんなどからまともなヒラギノフォントを買ってきてそちらを使う

今回は私が実際に使っているsizeThatFitsの実装を差し替える方法を紹介したいと思います。といってもまぁ結構簡単です。以下の様な実装になっています。

見ての通り、フォントファミリーがヒラギノ系であったら、
  • widthはceilする
  • heightはもともとの高さにfontのdescenderをfabsしてから足したうえでceilする
だけです。簡単でしょう? Apple爆発しろ

2015年10月30日金曜日

iOS デバイス上で YouTube の動画を扱うときの制限事項などまとめ

iOSのデバイス上でYouTubeの動画を扱うときに、PC上のブラウザとは異なった制約がかかる箇所がいくつかあるのでまとめておきます。

■iOSアプリでYouTube動画を扱いたいとき

youtube/youtube-ios-player-helper を使うのがもっともよいです。こちらはYouTube公式のライブラリとなっています。具体的な実装としてはUIWebViewを使い、その上にYouTubeのiframe APIを用いてiframeのプレイヤーをロードして表示する実装になっています。その他一部のAPIについてはObjective-C経由でそのまま呼び出せるようになっていて便利です。
こちらに動画ロード時に使えるパラメータの一覧があります。
またこちらにプレイヤーのAPIの一覧があります。
以上に記載がある機能は基本的に使えますが、一部例外があります。以下で説明します。

■iOSアプリでYouTube動画を扱いたいとき(AVFoundationで)

諦めてください。
実際にはやろうと思えば実は可能ですし巷にはそういうアプリもございますが、基本的にYouTubeが許可していない動画そのもののURLを直接探してきて叩く必要がありまして権利的に大変問題がございますので、まっとうなアプリを作ろうとお考えの方は潔く諦めていただくことを強くおすすめいたします。ただリジェクトはされていないところを見るとApple的にはYouTubeの都合なんぞ知ったこっちゃないというスタンスのようです。

■YouTube動画をインラインで表示したい

普通に問題ありません。ロード時にplaysinlineパラメータを1に設定しましょう。

■YouTube動画のUIをカスタマイズしたい

諦めましょう。インライン再生でもフルスクリーン再生でも、最初から用意してある見た目を使うしかありません。ただしコントロールを表示するしない程度はカスタマイズ可能です。ロード時にcontrolsやfsパラメータを渡しましょう。

■YouTube動画の一部だけを再生したい

可能です。ロード時にstartパラメータとendパラメータを指定すればオッケーです。

■YouTube動画を複数同時再生したい

複数のYouTube動画を並べるぶんには問題ありませんが、同時に再生となるとあんまり自信がないです(実機で試せていません)。ちなみにAndroidの場合はできないそうです。

■YouTube動画をオートプレイしたい

アプリであれば可能ですが、iOSのSafariでは不可能です。実装上許可されていません。
アプリの場合はUIWebViewのallowsInlineMediaPlaybackをYESに設定すればautoplayオプションが有効になります。先ほどのyoutube-ios-player-helperはこちらのオプションが有効になっています。

■YouTube動画をプログラム的にフルスクリーンに切り替えたい

いわゆる標準のフルスクリーンプレイヤーを使うという意味では不可能です。諦めてください。
詳細はこちら。
ワークアラウンドとしては、

  • 最初からフルスクリーンで表示する(これは可能です)
  • ボタンを押したら一旦現在の動画をアンロードして、フルスクリーンとして再度一からロードする(可能ですが遅いです)
  • プレイヤー領域を司るHTMLを操作してフルスクリーンに見せかける

などがあります。

■YouTube動画をミュートしたい

不可能です。諦めてください。
詳細はこちら。
AVFoundationの機能などを使っても基本的にアプリ全体をミュートにすることはできません。AVAudioSessionのmodeをAVAudioSessionModeMeasurementにすることでアプリ全体のオーディオプレイバックを止めるという裏ワザもありますが、この方法を使うとビデオの再生まで止まってしまうので意味が無いです。
ただしデバイスのmuteスイッチの状態やデバイス全体の音量は普通に動画の音量に反映されますのでご安心ください。


2015年10月19日月曜日

AppBank GAMESを退職していました

表題の件、私akisuteは2013年10月末日を持ちましてAppBank GAMES株式会社を退職したことをご報告いたします。短い間ではございましたが関係者皆様大変ありがとうございました。今後の予定につきましてはとりあえずのところ問題なくやっていけそうで助かっております。

みなさま3年間お疲れ様でした\(^o^)/

※すみません、なんかTwitter経由なりLINE経由なりで皆様から消しといたほうがよくねというご指摘を多数頂いておりまして、別に意地張るものでもないので以前の内容は削除させていただきました(´・_・`) 現職の会社から何か言われたってことはないのでごあんしんください\(^o^)/

2015年9月7日月曜日

Apple Watch アプリの実機インストールが失敗する時のメモ

Apple Watchアプリの実機インストールが失敗する際にチェックする事項をまとめてみました。Apple Watchアプリが実機インストールに失敗する原因は多岐にわたり、ここにまとめてある内容だけではとても網羅しきれませんが、何かの参考になれば幸いです。

以下、すべてwatchOS 1での結果です。watchOS 2ではまた何かが変わっているかもしれませんが、基本的なところは同じかと思います。

最初にやること

いずれの場合でもまず最初に必ずやることとして、XcodeのDeviceウィンドウを開いて実機のログを確認するようにしましょう。このログにApple Watchアプリをインストールするときのログも全て残るようになっていますので、一番の手がかりになります。

症状: 「インストールに失敗しました」ダイアログ

このダイアログが表示されるときは、殆どの場合iPhone本体にインストールされるApple Watch App ExtensionとApple Watch上にインストールされるApple Watch Appの両方がインストールに失敗していることが多いです。

失敗の理由ですが私が遭遇したものの中だとダントツ多かったのがコードサインエラーです。どうしてもインストールが成功しない場合は、Xcodeのビルド設定などから使用するProvisioning Profileを明示的に指定してやるとうまくいくことが多かったです。

このとき指定するProvisioning Profileは、Debug向けビルドであれば標準のデバッグ用のiOSTeam Provisioning Profile: *を使うか、または専用に用意したcom.example.myapp.watchkitappないしcom.example.myapp.watchkitextensionのようなApp IDを使うプロビジョニングを選びましょう。Release向けビルドの場合は必ず専用に用意したApp IDを使うプロビジョニングを選択する必要があるので、面倒でも必ずApple Watchアプリを作る際には新しくApple Watch App Extension用のApp IDとApple Watch App用のApp IDを作るようにしましょう。

症状:「インストール中です」が終わらない

このようなケースの場合は、どうやらiPhone本体にインストールされるApple Watch App Extensionのインストールには成功しているのだけれども、Apple Watch上にインストールされるApple Watch Appのインストールが何らかの理由で失敗している事が多いようです。

私が遭遇した失敗の理由ですと、例えばApple Watch AppのInfo.plistに不要な値を記載してしまったためにインストールにコケたということがありました。具体的にはLSApplicationQueriesSchemesとNSAppTransportSecurity(いずれもiOS 9向けのキー)をApple Watch AppのInfo.plistに記載していたのですが、インストール時にこれらが
(Error) WatchKit: validateWatchKitApplicationInfoDictionary, invalid Info.plist key 'LSApplicationQueriesSchemes'
のように言われて失敗してしまっていました。

watchOS 1の地点ではインストール時にかなり厳密に値をチェックしているようなので、Apple Watch App向けのInfo.plistには余計なものを記載しないほうがよさそうです。watchOS 2ではまた変わるかもしれませんが・・・

症状: インストールは正常に完了するのにアプリが全く立ち上がらない

大変稀なケースですが、インストールは完了するのにアプリが全く立ち上がらず、最初のアプリ名が表示されてクルクルが回ったまま永遠に先に進まなくなってしまう事があります。私が遭遇した場合の原因ですが、Entitlementsの指定が間違っていたときにこのような問題が発生したことがありました。通常アプリのEntitlementsは自動的にXcodeが生成して付与するため問題にはならないのですが、何らかの理由で自分でApple Watchアプリ向けにEntitlementsを自作して設定した場合は大変問題になります。

具体的には、Apple Watchアプリ向けにEntitlementsを自作する場合には必ず以下のようにしてください。

  • Apple Watch App Extension向けのEntitlementsには、必ずapplication-identifierとkeychain-access-groupsを含める。
  • Apple Watch App向けのEntitlementsには、application-identifierとkeychain-access-groupsのみを含め、その他のキーは絶対に指定しない。

application-identifierは指定がないとそもそもApple Watchアプリのインストールに失敗します。

keychain-access-groupsはアプリ内でKeychainを一切使っていなくても必ず含めてください。どうやらiPhone本体とApple Watchアプリ間の通信の際にこのEntitlementsをこっそりシステムが使用しているようで、keychain-access-groupsが指定されていないと冒頭で紹介したような事態が発生するようです。エラーメッセージも何も一切表示されなかったため調査に大変苦労しました。

2015年7月31日金曜日

iOS 9 の split screen に対応するサンプルプロジェクトを作成してみました

WWDCでの発表でもありましたとおり、iOS 9からはiPadで画面を二分割して複数のアプリケーションを同時に実行することができるようになります。このSplit Screen機能に対応するためには、iOS 8で導入されたAdaptive User Interfaceを活用する必要があります。

参考:



Adaptive User Interfaceな実装をする上で最も簡単な対応方法はひとつのUIViewControllerで複数のSize Classeに対応することです。大体のケース、例えばApple標準のMapアプリやメールアプリなどはこれで問題がないのですが、例えば横方向がRegularサイズの場合(=iPadやiPhone 6 plusなど横に広いデバイスの場合)はUICollectionViewを使ってタイル上に広くアイテムを並べ、横がCompactなデバイス(縦向きのiPhoneなど)の場合はUITableViewを使って縦に多数のアイテムを並べたいという需要があったりします。この場合、ひとつのUIViewControllerでどちらにも対応するのがかなり困難になり、シンプルな実装になりません。

そこでSize Classに応じてdynamicに複数のUIViewControllerを差し替えて表示するサンプルを作ってみました。



特徴:

  • Size Class変更時にきちんとアニメーションします
  • 当然Split Screenにも対応しています


iOS 9は既存のコードをAdaptive User Interfaceに対応させる良い機会だと思いますので、これを機会に皆さんも自分のアプリを見なおしてみてはいかがでしょうか?

2015年4月10日金曜日

Apple Watch の実機を触ってわかった、アプリ開発者が抑えておくべきポイント


本日からついにApple Watchの実機がお目見えとなりました。私も早速Apple Storeに行って試着・試用してきたのですが、予想以上にアプリ開発に影響がありそうな点が多数見つかりましたので、思うところをブログ記事にまとめて公開しようかと思います。

■小さい、とにかく小さい

Apple Watchの実機を身につけてまず最初に感じるのがその圧倒的な小ささです。この小ささというのは

  • これまでのAndroid Wearデバイスのどれと比べても感じる相対的な小ささ
  • Apple Watch上で表示されているUIを見て感じる絶対的な小ささ

の2つの要素から感じられます。

試しに私が身につけているAndroid WearデバイスとApple Watch Standard 42mmを並べて写真をとってみたのですが、見ての通り42mmモデルですら表示領域がずいぶんと小さいのがわかります。

その上Apple WatchのUIは全体的にAndroid WearのUIと比べて密度が高い用に感じられます。こちらのブログに具体的な例があるのでぜひ参照していただきたいのですが、見ての通り同じアプリでもApple Watchのほうが詰まったUIになっています。ただでさえデバイス自体が小さめな上に密度の高いUI、具体的にはアプリ内で常時上にナビゲーション領域が表示されたりする、ということでなおさら小ささが際立っているわけです。

小ささをより体感するために、iPhone 6の画面でApple Watch 38mmの画面サイズを表現してみました。

アイコンが一つにラベルが一つプラスアルファ程度がせいぜいの大きさしか無い、というのがよく分かるかと思います。

したがって繰り返し繰り返し随所で述べられていると思いますが、画面上に表示する要素は徹底的に少なくする必要があります。私も十分に少ない要素だけを画面に表示するように心がけていたつもりでしたが、実際にデバイスに触れたあとに見返すとまだまだ要素が多すぎるぐらいです。少なすぎるのではと心配になるぐらいまで減らしてちょうどいいのではないでしょうか。

Appleの標準のアプリなどでかなり高密度なUIを採用しているものもありますが、そこは真似しないほうが良いと考えています。具体的には標準のマップアプリなどは38mmモデルの上では細かすぎて地図を読み取るのが極めて困難でした。

■Glanceこそがすべて

Apple Watchのインターフェースのナビゲーションは以下の図のようになっています。

基本は時計フェイスが表示されていて、そこから竜頭を押すとHomeに遷移してアプリを選択して起動することができます。時計フェイスを下にスワイプすると上からNotification Centerが表示され、上にスワイプすると下からGlanceが表示されます。Glanceは左右スワイプで次々に閲覧することができます。感覚的にはGlanceはiOSデバイスにおけるWidgetのようなもので、常時Widgetが時計フェイスの下に並んでいるようなイメージをするとわかりやすかったです。

このインターフェースの中でアプリができることで、最も重要になってくるのがGlanceです。操作してみてわかったのですが、Home画面からアプリを起動するのはただでさえ小さいWatchの画面上に無数の小さいアイコンが並ぶため困難苦痛を極めます。したがって必然的にアプリの状態を確認したりアプリを起動するのはGlanceを使うのが最も楽でスピーディで良いということになります。GlanceこそがApple Watchアプリにおけるすべての窓口と言えそうです。ここをどれだけ便利に使いやすく見せるかによってアプリの価値が変わってくるかもしれません。

■ネイティブアプリは速いが転送は遅い?

気になるApple Watchの動作速度ですが、まず通常の用途ですとかなりサクサクと動作しました。時計フェイスから通知センターに遷移したりGlanceを見たり、Glance間を切り替えたりするぶんには素晴らしい応答速度で、手元のAndroid Wearデバイスよりも機敏に感じました。

これがアプリとなってくるとだんだんと遅さが感じられる場面が出てきます。気になった点としては、

  • いくつかのGlanceについてロードが終わらない、ないしロードが遅すぎる。マップ・天気・株価が該当。
  • GlanceまたはHomeからのアプリの起動が遅いときがある。フィットネスで該当。
  • フィットネスで「開始」ボタンを選択してから実際に開始するまでに明らかに感じられる遅れがあった。
  • マップアプリについてはロードが遅く、地図が表示されるのも遅い。

これらから推測するに、おそらくネイティブでアプリが動作している部分に関しては素晴らしいパフォーマンスが得られているものの、本体側のiPhoneからデータを転送している箇所に関しては顕著にパフォーマンスが落ちているのではないか思われます。

今回体験する事ができた実機にはサードパーティ製のアプリが入っていなかったので、我々開発者が作ったアプリに関してどの程度のパフォーマンスが得られるのかは全く不明ですが、この調子ではあまり良い結果が得られないかもしれません。今後のSDKの拡張でApple Watchネイティブのアプリが作れるようになるまでは、本体からデータを転送する頻度および転送量を少しでも削減できるようなつくりを目指すしかなさそうです。

■妄想とか将来の話

その他現状はサードパーティ開発者からは使えないのですが、将来的に面白くなりそうだと思った点を挙げます。

まず竜頭コントロールについてですが、現状竜頭コントロール入力をアプリ側から取得することができないのはみなさんご存知のとおりです。触った感じ竜頭自体は非常に良く出来ていたのですが、画面のどの箇所が竜頭でコントロールできるのか出来ないのかがいまいちよくわからないという問題があるように思えます。最も顕著な例は時計フェイスのカスタマイズUIで、これはカスタマイズする箇所をタップしてから竜頭で項目を選択するという仕組みになっているのですが、直感的に非常にわかりづらかったです。きちんと考えて統一的に使われていれば便利かもしれませんが、画面のタッチとの併用がほぼ必須なため竜頭だけでコントロールできなかったりなど、課題が山積みのように感じます。

逆にForce Touch(強く力を込めて押しこむようにタップする)機能ですが、これは非常に優れているように感じました。ダブルタップと特性は似ていますが、精密動作を必要とせずTapticエンジンによるフィードバックによって入力成功が伝わる点を考えてもダブルタップより圧倒的に優れている入力方式と言えます。現状Force Touchは自由に使うことができずメニューの表示用途に限定されていますが、これはまずForce Touchという操作の存在を確実にユーザーに理解してもらうという意味で良いと思います。この操作が広まればゆくゆくはiPhoneやiPadにもForce Touch搭載されることが確実でしょうし、アプリは積極的にForce Touchを取り入れていくべきと思いました。

将来的にForce TouchがiPhoneに導入されるとなると、iOSのAndroidに対する現在の弱点であるメニューボタンの不在を補う重要な役割になってくるかもしれませんね。さらにゲームでも大変有効に使える入力方式に間違いありません!夢が広がります。

Force Touchといえばその対となるTapticエンジンも非常に素晴らしかったです。Force Touch時のフィードバック、通知時のフィードバック、友人へのハートビートの送信、すべてで全く異なる触覚が伝わってくるのがまさに見事でした。まったく画面を見なくても触覚の違いだけで何が起こっているのかを判別できるほどです。現状Tapticエンジンを自由に触ることはできませんが、もし開放されたらTapticからのフィードバックだけで画面を全く見ないでも十分に使えるアプリが作れるかもしれません。全くユーザーを煩わせることない究極のUIになりうるかもしれませんね。

最後にGlanceについてちょっと触れます。基本的に現在サードパーティのアプリがGlanceでできるのは情報を表示するだけで、Glanceがタップされた時の挙動もWatchアプリが起動するだけに固定されてしまっています。

ところがAppleのネイティブアプリである心拍数Glanceについては、なんとGlanceが表示された瞬間に心拍数が自動的に計測開始され、さらにボタンをタップするとダイアログが表示されるというつくりになっていました。すなわち機能が開放されていないだけでリッチなGlanceを作ることも可能なようです。先にGlanceこそがすべてだと述べましたが、このリッチなGlanceを作る機能が開放されたらApple Watchのサードパーティアプリの可能性は更に広がると思います。

2015年3月16日月曜日

WatchKit 向けの UIImage Animation を簡単に実装するためのライブラリ ParaMangar を作りました

いよいよApple Watch発売日まで1ヶ月ということで皆様精力的にWatch向けのアプリを作成されているのではないかと思いますが、現状のWatchKitで誰もが一度はマジギレ不満に思う点がアニメーションです。

こちらのブログにある通り、WatchKit向けのアニメーションを作成するのは現状極めて面倒と言わざるを得ません。

http://d.hatena.ne.jp/shu223/20150214/1423901142

そこでiOS側でUIViewを今までどおりレンダリングして、その結果をファイルにしたりUIImageにしてWatchKitに渡せばいいじゃない!というライブラリを書いてみたので公開いたします。

https://github.com/akisute/ParaMangar

このParaMangarを使うとこんな感じでアニメーションが作れます。


ライセンスはMITです。
あまりテスト出来ていないのでバグだらけかもしれませんが、ご意見issueなどgithubでお寄せいただければ幸いでございます!!

2015年2月2日月曜日

YouTube などのフルスクリーンで再生される UIWebView の動画をプログラムから終了させる方法

UIWebViewでyoutube.comやvine.coなどのサイトの動画を開いた場合、動画がフルスクリーンの専用動画プレイヤーViewControllerで再生されることがあります。このフルスクリーン動画を閉じる方法です。

方法1: 安全な方法

JavaScriptのFullscreen APIを使って安全に閉じることができます。iOS 6以上で動作確認済みです。

Fullscreen APIについては以下の資料が詳しいです。


方法2: animated, completionの制御もしたい場合

ここからがお待ちかねの黒魔法になります。
先ほどのJavaScriptを使った方法ではフルスクリーン動画を閉じる際のアニメーションを制御できません(必ずアニメーションが発生します)。またフルスクリーン動画を閉じ終わったタイミングのcompletion handlerが存在しません(JavaScriptでハンドルしようにも、videoタグのonendedイベントは通常の再生終了時にもイベントが飛ぶ上にアニメーション終了時ではなく再生終了時にイベントが飛ぶため使いづらい)。

通常はこれらが問題になることはまずありませんし、万一あったとしても仕様のほうを変えるほうが適切ですが、何らかの止むに止まれぬ理由によりなんとかしなければならなくなる場合が稀によくあります。

そこで画面上にUIWebView経由で表示されている動画プレイヤーViewControllerをビュー階層をたどって見つけ出し無理矢理dismissViewController:completion:で消すという方法を取ります。iOS 6以上にて動作確認済みです。ただしiOS 7以下の場合とiOS 8の場合で全く構造が異なり、将来にわたって動作するか非常に怪しいです。

Private APIの名前がバリバリ含まれるコードになっておりますので審査に出すアプリには使用しないことを強くおすすめさせていただきます。

2015年1月15日木曜日

ReactiveCocoa を Swift から使ってみた(2) KVO編

前回の記事から引き続き、ReactiveCocoaを触ったりしています。FRPの概念に慣れてくると通常のプログラミングスタイルでは得られない知見に遭遇出来てなかなか面白いです。

今回はまずSwiftでReactiveCocoaを学ぶときに参考にするドキュメントについてご紹介したいと思います。といってもSwiftに特化したドキュメントはほとんど存在しないため、Objective-CでReactiveCocoaを学ぶときのドキュメントを見て学ぶしか無いのが現状です。

私は個人的にはサンプルコードを直接触るほうが性に合っているようなので、以下のオープンソースのReactiveCocoaで作られたアプリのコードを見ています。
https://github.com/AshFurrow/C-41
https://github.com/jspahrsummers/GroceryList
GroceryListのほうがより本格的にFRPっぽい書き方をしているのでオススメです。実際にコードを動かしたい場合はC-41のほうが比較的簡単に動かしやすいと思います(それでも使われているライブラリが古いためビルドを通すのが大変だったりしますが・・・)

SwiftでReactiveCocoaを使っている例としてはCarthageが良いと思います。ただしMacのコマンドラインアプリなのでUIまわりがらみのサンプルとしては余り参考になりませんでした。
https://github.com/Carthage/Carthage

例えばReactiveCocoaを使う上でどうしても欠かせないのがRACObserveを使った特定プロパティに対するKVOのシグナル化です。これにより特定のプロパティが変化した時にシグナルを受け取ることができるようになります。通常Objective-CでReactiveCocoaを使う場合は標準のRACObserveというマクロを使用すれば良いのですが、Swiftではマクロが使用できないため他の方法を採用する必要があります。

調べてみたところ、幸いにしてRACObserveマクロは単なるNSObjectのrac_valuesForKeyPathメソッドのラッパですので、以下のようにしてrac_valuesForKeyPathを使用すれば解決できます。


ここで注意することとして、KVO対象となるプロパティにはdynamic修飾子を付ける必要があります。Objective-Cにもdynamic修飾子はありましたが、Swiftのdynamic修飾子はObjective-Cのものとは意味が異なりdynamic修飾子を付けたプロパティについてObjective-Cと同様に動的プロパティアクセスを行うようにする(具体的にはobjc_msgSendする?)という意味があります。詳細については以下の記事を参照してみてください。
http://stackoverflow.com/questions/24092285/is-key-value-observation-kvo-available-in-swift#comment39273366_24092370
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html

2015年1月1日木曜日

Swiftプログラマ格付けチェック (2015新年スペシャル)

Swiftプログラマ格付けチェック

今回一流のSwiftプログラマの皆さんに格付けチェックしていただくのはこちら!

JSONライブラリ

です!

ひとつは日本が誇る一流プログラマ、dankogai氏が作成されたJSONライブラリ、githubスター数312
ひとつは当ブログ管理人三流プログラマ、akisuteが適当にググって見つけたJSONライブラリ、スター数16
となっております。
皆様にはこの2つのうちからdankogai氏のライブラリを当てていただきます!

Aのライブラリ
Bのライブラリ
(コメントはすべて削除しています)

それでは正解だと思ったほうの部屋に入っていただきましょう!

Aが正解だと思った人の部屋
Bが正解だと思った人の部屋