Tokyo Server-Side Swift Meetup で「swift build と Xcode での build の違い」について発表してきました

Tokyo Server-Side Swift Meetup #4」で発表させていただきました。

このイベントがきっかけで server side swift に初めてふれてみたのですが、久しぶりに web の開発をした気がしてとても楽しかったです。 Xcode で使うことを気にしなければ簡単に試せたのでイメージはすごい変わりました。

資料の中(P.43)に書いていたのですが SPM で dylib を Build するものに関しては、どうしても Xcode 側でうまく Build することができませんでした。

framework と dylib が絡む Xcode の設定がおかしいのかなと。 とはいえ clang-framework で compile しようとしているのに、なぜ -l しようとしてるのかもよくわかりません。

仕方ないので力技で swift build で作成した dylib を参照するようにして解決しました。 この辺りがなぜなのか分かる人がいれば教えてもらえるとありがたいです。

noppoMan/Slimane は現時点で気軽に試せるのでオススメです。(swift build で使う分には。)

ではでは

Xcode Editor Extension と Xcode8 で Plugin が動かなくなったことについて発表してきました

FiNC WWDC振り返り勉強会」と「potatotips #30 (iOS/Android開発Tips共有会)」で発表させていただきました。

内容としては Xcode Editor Extension についてと、既存の Xcode Plugin が Xcode8 では動かなくなるのを回避する方法があるよ。 という内容を話しました。

ngs さんと同じ内容を話していたので該当箇所は結構削ったので Xcode Editor Extension について、そちらの資料を見るのがいいかなと思います

ja.ngs.io

Xcode の非証明バイナリを作る方法を gist にスクリプトでまとめておきましたので自己責任でお願いします。

非証明バイナリにするというのはどうなのかといった話もあると思いますが、それでも Plugin を使いたい人もいると思うのでそういう人にはいいんじゃないでしょうか。 個人的には DL してきて正しい証明されてるものと確認した後に抜くのでいいんじゃないかなと思ってます。

ただ非証明 Xcode でも動かない Plugin もあって、それの原因がよくわかってません。 もうすこし調べれば分かるかなと思ってます。

Xcode Source Editor Extension はいい仕組みだと思うので、しばらくの間の移行期はこれで耐え忍ぶしかないかなと個人的には思っている次第です。

github.com

このようにフィードバックを送って API を増やしていってもらえれば、1年もすればすんなり移行できてたりするかもしれませんね。 まぁ Plugin をつかわないように調教するのもひとつの手段だとは思いますが。

Anglerfish について potatotips#29 で LT してきました

f:id:dealforest:20160527161806p:plain

potatotips #29 (iOS/Android開発Tips共有会) で LT をしてきました。 主催の方々お疲れ様でした。とても楽しめました。

id:koogawa さんの 「 2016/5/25 #potatotips #29 (iOS/Android開発Tips共有会) に参加してきたよ」が当日の様子がわかりやすいのでオススメです。

発表内容

今回は Anglerfish という Xcode Plugin を作ったのでそれについて話しました。 Anglerfish とは iPhone の simulator を使用順にソートする Xcode Plugin です。

github.com

まだ Xcode 起動時の情報を基にしてソートするしかできないのですが今後はもう少し機能を追加する予定です

  • simulator のソート順をリアルタイムに反映 v0.1.1 実装済
  • ソートの on/off を切り替え
  • ショートカットで deivce と simulator を切り替え

命名は niwatako 氏にお願いしました。 いつもありがとうございます。

使ってくれたら何かフィードバックがあると嬉しいです、ではでは

xcode-install を利用した Xcode のバージョン管理をやってみた

Xcode のバージョン管理するが面倒くさい!そう思ったことはありませんか?

App Store から DL しても途中で失敗したりなかなかうまいこといきません。 仕方がないので Developer Center から直接 DL してきます。 そして古い Xcode.app をリネームして残しておき、新しいのと入れ替える。

そう、めんどくさいんです。。。

そんな時に neonichu/xcode-install と出会いました。 作者は try! Swift でも登壇されていた Boris Bügling さんです。

これは rbenv のような **env 系と似たような interface を持つ CLIXcode のバージョンを管理するのに特化しています。

これがとても便利だったので紹介します。

xcode-install の使い方

Installation

gem install xcode-install

Xcode を DL する際に Developer Center にログインする必要があります。

毎回ログイン情報を入力するのもめんどくさいので、XCODE_INSTALL_USER, XCODE_INSTALL_PASSWORD環境変数を指定しておくと楽になります。

password はいやだって人は ID だけでも設定しておくと、一度パスワードを入力すると次回からは keychain から取得してくれるのでそれでもいいかなと思います。

ここの部分は fastlane/credentials_manager を使っているようです

Usage

$ xcversion update
$ xcversion install 7.3.1

仕組みとしては簡単で Xcode_x.x.x.dmg を cache ディレクトリに保存し、それを /Application/Xcode-x.x.x.app にインストール後、xcode-select を実行してバージョンを切り替えてくれます

以下、ざっと使うコマンドを紹介していきます

インストール可能なバージョン一覧を表示

$ xcversion list
7
7.0.1
7.1
7.1.1
7.2
7.2.1 (installed)
7.3 (installed)
7.3.1 (installed)

--all をつけると、さらに古いバージョンも表示されます

インストール可能なバージョン一覧を更新

$ xcversion update

インストール済みのバージョン一覧を表示

$ xcversion installed
6.4     (/Applications/Xcode-6.4.app)
7.2.1   (/Applications/Xcode-7.2.1.app)
7.3     (/Applications/Xcode-7.3.app) 
7.3.1   (/Applications/Xcode-7.3.1.app)

DVTPlugInCompatibilityUUID を一覧で表示するオプションを追加した PR がマージされたので 1.3.0 の次のバージョンから使えるようになるでしょう。 Xcode Plugin を作ってる(使ってる)人には大助かりですね。

はやく使いたい方は master からインストールしてもらえれば使えます

$ xcversion installed --uuid
6.4     (/Applications/Xcode-6.4.app)     7FDF5C7A-131F-4ABB-9EDC-8C5F8F0B8A90
7.2.1   (/Applications/Xcode-7.2.1.app)   F41BD31E-2683-44B8-AE7F-5F09E919790E
7.3     (/Applications/Xcode-7.3.app)     ACA8656B-FEA8-4B6D-8E4A-93F4C95C362C
7.3.1   (/Applications/Xcode-7.3.1.app)   ACA8656B-FEA8-4B6D-8E4A-93F4C95C362C

利用する Xcode を変更

$ xcversion select 7.3.1

Simulator の install 状況を確認

$ xcversion simulators
Xcode 7.3.1 (/Applications/Xcode-7.3.1.app)
iOS 8.1 Simulator (installed)
iOS 8.2 Simulator (installed)
iOS 8.3 Simulator (installed)
iOS 8.4 Simulator (installed)
iOS 9.0 Simulator (installed)
iOS 9.1 Simulator (not installed)
iOS 9.2 Simulator (installed)
tvOS 9.0 Simulator (installed)
tvOS 9.1 Simulator (installed)
watchOS 2.0 Simulator (installed)
watchOS 2.1 Simulator (installed)

Simulator を install

$ xcversion simulators --install='iOS 9.1 Simulator'

Xcode_x.x.x.dmg を削除

一度 DL した Xcode_x.x.x.dmg は保存されたままになります。 ディスク容量が気になる方は cleanup を実行して削除してください

$ xcversion cleanup

はまりどこ

Developer Center にログインすると利用規約への同意画面が表示されるような状態のアカウントを使うと Xcode の DL に失敗(タイムアウト)します。 普段使っていないアカウントを使う場合は一度ログインして確認しておきましょう。

まとめ

xcode-install を使うことによって Xcode のアップデートが以下のような手順になります。

$ xcversion update             // 一覧更新
$ xcversion list               // 入れたいバージョンを確認
$ xcversion install x.x.x      // インストール

これだけで最新の Xcode が使えるようになり、バージョン管理も行ってくれます。 これで Xcode のヘッダーの diff をとってニヤニヤする作業もしやすくなりますね。

UIWebView で input[type=file] が正常に動かない対応について -決定版-

基本的なことは「iOSアプリのwebviewでinput[type=file]が正常に動作しない時」見てもらえれば分かるかなと思います。

簡単に説明すると画像を選択後 Image Picker を閉じようとした際に WebView をモーダルで表示していた場合、モーダルごと閉じられてしまうということです。

どういう view 構造にするかにもよりますがこのような構造でモーダルを表示していたとします。

[modal の view 構造]
UINavigationController
  └── UIViewController
      └── UIWebView

この場合 UINavigationController に対して解決方法を適応してやればいいわけです。

extension UINavigationController {

    override public func dismissViewControllerAnimated(flag: Bool, completion: (() -> Void)?) {
        if presentedViewController != nil {
            super.dismissViewControllerAnimated(flag, completion: completion)
        }
    }
    
}

ただしこれだと問題があります。

それは UIActivityViewController を使って新たに ActivityItem を追加しようとした時です。 追加しようとしたモーダルが閉じれなくなってしまうんですね。。。

f:id:dealforest:20160330013354p:plain

というわけで、その状況にも対応しようとするとこのようになります

extension UINavigationController {

    override public func dismissViewControllerAnimated(flag: Bool, completion: (() -> Void)?) {
        if presentedViewController != nil || presentingViewController is UIActivityViewController {
            super.dismissViewControllerAnimated(flag, completion: completion)
        }
    }
    
}

HTML の方が楽な場合(複雑な投稿画面等)とかもあるのでそういう時では使っていきたいんですが、なかなか一筋縄ではいきませんね。 WKWebView では同じ現象が起きるかどうかは知りませんが起きないことを祈ります。

DerivedData を簡単に消せる Xcode Plugin

懇親会における英会話のプロトコル」を見て try! Swift に行くまでに何か簡単に話せる小ネタがあればなと思って作りました。

Swift を書いているとよくあるのですが、頻繁に中間データが壊れてしまいます。 そのため DerivedData を消すとなおったりします。

消す方法は「Window」->「Project」 を開いて該当するプロジェクトの DerivedData を削除するといったことをしないといけません。 めんどくさいですね。

Clean を実行した時に自動で消してくれたらいいのにと思っていました。

と、いうわけで作りました。

dealforest/Cichlid(シクリッド)

f:id:dealforest:20160301011702p:plain

由来は掃除魚(ベラ・ハゼ・シクリッド・ナマズなど)のベラを採用しました。 Swift で作った Xcode Plugin でコード量も少ないので見れば簡単にわかるかなと思います。

ざっと作った感じですが、意外に便利なのでぜひ使ってみてください。 yidev 第22回勉強会 でこの辺りの話をしようかなと思います。

それでは try! Swift で会いましょう。

isRegisteredForRemoteNotifications が正常に動かなかったときの対処

PUSH 通知が有効/無効をアプリ上で表示(UISwitch等)して有効にしようとした場合にアプリの設定画面に遷移して有効にしてもらう。 といったことを実装することがあるかもしれません。

実装は「[Swift] iOS でプッシュ通知の有効・無効を判定する」を見てもらえれば分かりやすいと思います。

それが意図した通りに動かないケースに遭遇しました。

isRegisteredForRemoteNotifications で有効/無効を確認するのですが、PUSH 通知を許可していないのに true になったり、再インストールした時にダイアログが出ている状態なのに true になったりなどです。

解決方法

registerForRemoteNotifications を呼び出しているしている場所を確認してください。

ドキュメント を見ると application:didRegisterForRemoteNotificationsWithDeviceToken:application:didFailToRegisterForRemoteNotificationsWithError: 以外からの呼び出しは推奨していないようです。

今回はこれが原因でした。 なので 上記の delegate の中から呼び出すようにすればなおりました。

呼び出した場合どうなるか

解決方法で書いたようにちゃんと delegate の中で registerForRemoteNotifications を実行していればこんなことにはならないのですが、何かの役に立つかもしれないので検証した結果を書いておきます。

iOS9.2.1 で下記のコードで検証しました。

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    ...
    
    let types: UIUserNotificationType = [ .Badge, .Sound, .Alert ]
    let settings = UIUserNotificationSettings(forTypes: types, categories: nil)
    application.registerUserNotificationSettings(settings)
    print("before \(application. isRegisteredForRemoteNotifications)")
    application.registerForRemoteNotifications()
    print("after \(application. isRegisteredForRemoteNotifications)")
    
    ...
}

検証した限り初回インストールと、再インストールの場合アンインストールした時の通知設定の状況に左右されます。

初回インストール

isRegisteredForRemoteNotifications が正常な値を返す

再インストール

前回のインストールの状況時の PUSH 通知の許可状況に依存します。

アンインストール時に通知を不許可

isRegisteredForRemoteNotifications が正常な値を返す

isRegisteredForRemoteNotifications
before false
after false

アンインストール時に通知を許可

この時が曲者で、初回起動時のみ挙動が変わります。

registerForRemoteNotifications を呼び出したら isRegisteredForRemoteNotificationstrue になります。 そしてダイアログに出ているのに関わらず application:didRegisterForRemoteNotificationsWithDeviceToken: が呼び出され device token が取得できてしまいます。。。

isRegisteredForRemoteNotifications
before false
after true

次回起動時からは通知ダイアログの選択に依存し正常に動作します。

これのタチの悪い所はアンインストール前の通知の有効/無効に依存することです。 状況が不安定になってしまってデバッグ時に困るので delegate の中で registerForRemoteNotifications を呼び出すようしましょう。 (ドキュメントにはそう書いてありますが。。。)

あまり関係ありませんが、いつの間にかアプリを入れなおすと通知のダイアログが毎回出るようになったんですね。 以前は端末の時計を進めて再起動したりしないといけなかったのでお手軽になりましたね。