文字っぽいの。

文字を書いています。写真も混ざります。

Storyboardを利用したViewControllerのインスタンス生成を楽にする

背景

1つのStoryboardに1つのViewControllerという運用をしていると、ViewControllerのインスタンス生成するコードを書くことが多くなると思います。雑に書くと、

let storyboard = UIStoryboard(name: "SomeViewController", bundle: .main)
let vc = storyboard.instantiateInitialViewController() as! SomeViewController
present(vc, animated: true, completion: nil)

こんな感じでViewControllerを作ってモーダル表示できますね。

さて、これだと生成する度にこのコードを書かないといけないので大変です。なので生成したいViewController側に書いちゃいましょう。

final class SomeViewController: UIViewController {
    
    class func viewController() -> SomeViewController {
        let storyboard = UIStoryboard(name: "SomeViewController", bundle: .main)
        let viewController = storyboard.instantiateInitialViewController() as! SomeViewController

        return viewController
    }

}

...

// 使う時
let vc = SomeViewController.viewController()
present(vc, animated: true, completion: nil)

1回定義してしまえば使う時はさっと使えて便利です。実装も SomeViewController 側にまとめられるので安心。

ただ、この方法だとViewControllerを作る度に class func を作る必要があってわりと手間です。また、よくある事故として UIStoryboard(name: "SomeViewController", bundle: .main) のnameを変え忘れ問題があります。めんどくさいので書かなくても済むようにしたいです。

解決法

まずは次のような protocol-extension を書きます。

protocol Instantiatable {
    static var storyboardName: String { get }
}

extension Instantiatable where Self: UIViewController {
    static var storyboardName: String {
        return ""
    }

    private static var _storyboardName: String {
        if storyboardName.isEmpty {
            return className
        } else {
            return storyboardName
        }
    }

    private static var storyboard: UIStoryboard {
        return UIStoryboard.init(name: _storyboardName, bundle: nil)
    }

    private static var className: String {
        return String(describing: Self.self)
    }

    static func instantiateFromStoryboard() -> Self {
        guard let vc = storyboard.instantiateInitialViewController() as? Self else {
            fatalError("Can no instantiate \(Self.className) from \(storyboardName).storyboard")
        }
        return vc
    }

    static func instantiateFromStoryboard(withIdentifier id: String) -> Self {
        guard let vc = storyboard.instantiateViewController(withIdentifier: id) as? Self else {
            fatalError("Can no instantiate \(Self.className) from \(storyboardName).storyboard with id: \(id)")
        }
        return vc
    }
}

あとは自作のViewControllerで、

final class SomeViewController: UIViewController, Instantiatable {
// ...
}

とすれば

let vc = SomeViewController.instantiateFromStoryboard()
present(vc, animated: true, completion: nil)

と使うことができます。簡単ですね。もし、Storyboard IDを元にしたいのであれば

let vc = SomeViewController.instantiateFromStoryboard(withIdentifier: "SomeID")

とすればよいです。また、.storyboardの名前がViewControllerのクラス名と異なっている(今回の例の場合は SomeViewController.storyboard ではない)場合には

final class SomeViewController: UIViewController, Instantiatable {
    static let storyboardName = "OtherStoryboardName"

    //...
}

のように storyboardName を指定すれば良いです。この場合は SomeViewController.storyboardではなく OtherStoryboardName.storyboard を見に行きます。

まとめ

これで Instantiatable protocolを実装すれば、追加のコード無しでインスタンスを簡単に生成できるようになりました。便利ですね。

参考

Slackのstatusに六曜を表示してデプロイの無事を祈る

六曜とは

六曜(ろくよう、りくよう)は、暦注の一つで、先勝(せんしょう)・友引(ともびき)・先負(せんぷ/せんぶ)・仏滅(ぶつめつ)・大安(たいあん)・赤口(しゃっこう)の6種の曜がある。 日本では、暦の中でも有名な暦注の一つで、一般のカレンダーや手帳にも記載されていることが多い。今日の日本においても影響力があり、「結婚式は大安がよい」「葬式は友引を避ける」など、主に冠婚葬祭などの儀式と結びついて使用されている。

Wikipedia

とのことです。

意味があるか

「今日は仏滅なのでデプロイやめましょう。」とか「今日は先勝なので午前中にデプロイしちゃいましょう。」とか話せる。話さないかもしれない。

様子

こんな感じで、意味も教えてくれる。

意味は国会図書館のWebサイトにまとまっていたのでそちらを利用。文章が短くできるものは短くした。

コード

六曜の計算は

という便利APIがあったのでそれを利用させてもらっている。コードはこちら。

このスクリプトをHerokuで定期的に実行している。

SLACK_OAUTH_ACCESS_TOKEN の取得方法は、こちらのQiitaが参考になった。

qiita.com

特にGemも使っていないので、適当にコピペしてどっかで定期的に実行すれば動くと思う。

まとめ

1回Swiftで書いたんだけど、Herokuでうまく動かなくてRubyで全部書き直していて不毛。ちなみに赤口の日に書いていた。

Swift 4.2でコマンドラインツールを作るときに外部ライブラリを使う方法

開発環境

手順

$ mkdir SwiftCommandSample
$ cd !$
$ swift package init --type executable

中身はこうなっている。

.
├── Package.swift
├── README.md
├── Sources
│   └── SwiftCommandSample
│       └── main.swift
└── Tests
    ├── LinuxMain.swift
    └── SwiftCommandSampleTests
        ├── SwiftCommandSampleTests.swift
        └── XCTestManifests.swift

4 directories, 6 files

Package.swift をいじる。自動生成されたものがこれ。

ここで、試しに APIKit を導入してみる。次のように書き換える。

これを書いたあとに

$ swift build

を叩けば、依存が解消される。また、Xcodeでコードを書きたい場合は、 swift build をしたあとで、

$ swift package generate-xcodeproj

をすると SwiftCommandSample.xcodeproj が生成される。swift package generate-xcodeproj は依存するライブラリを変更して、 swift build する度に呼ばないといけない。呼ばないとライブラリの変化が.xcodeproj に伝わっていないのでビルドできない。

これでAPIKitが使えるようになったので、あとはコードを書くだけで良い。

Macで英語入力時にキーを長押ししたときに出るウムラウトとかのポップアップを出さなくする

f:id:FromAtom:20180914133559p:plain

新しいMacをセットアップする度に、方法を忘れるので書いておく。

$ defaults write -g ApplePressAndHoldEnabled -bool false

terminal.app とかで入力してから、再起動すると消える。

#iOSDC Japan 2018 に参加して、発表もして、感情になりました。

f:id:FromAtom:20180907223345j:plain

iOSとその周辺技術に関するエンジニアのためのカンファレンスである、iOSDC Japan 2018(iOS Developers Conference Japan 2018)に参加・発表してきました。

iosdc.jp

参加者として

去年初めて参加してとても楽しかったので、今年も参加しました!もちろん、とても楽しかったです!

昨年もとても素晴らしいホスピタリティを誇るカンファレンスだったのですが、今年もレベルアップして最高のホスピタリティに満ちあふれていました。

今年も美味しいランチが待っていました。去年は少し物足りないなぁという感じていたのですが、今年はガッツリとご飯もの!味も美味しいし、お腹もいっぱいで幸せでした。

f:id:FromAtom:20180901120759j:plain

そして食後は、無限コーヒー。ホットだけではなくアイスもあって最高ですね。しかもこのコーヒー美味いんですよ。最高。 食後に美味いコーヒーを飲みながらトーク開始を待てるなんて、なんて贅沢なんでしょう。ここは国際学会か?

f:id:FromAtom:20180902154655j:plain

懇親会ではビールに日本酒と最高の銘柄が揃っておりました。毬花はうまいぞ。いい感じの枡もあったんですが、入手に失敗しました。くやしいー。

f:id:FromAtom:20180901185327j:plainf:id:FromAtom:20180901185305j:plain

また、会場全体には無線LANが飛んでおり、いつでもどこでも快適なインターネット環境を得ることができました。 これもひとえにネットワークスタッフのみなさまのおかげです。ありがとうございます! 来年はグローバルIPを使って、Server Side SwiftなWebアプリが公開できる準備をしていきたいと思いました。

スピーカーとして

こちらの内容を話をしました。iOSDC1日目、朝イチ、Track Cという夜型人間に厳しい枠でした。 内容のニッチさと、時間帯、そして裏番組が

の2つでかなり強かったので、あまり人は来ないだろうと安心しきっていたのですが、なんと満員御礼。 わずかですが立ち見も出ており、嬉しいやら申し訳ないやら。

時間の都合上、AVPlayerの基本的な使い方をすべてカットしたため、本当に初心者の人には何もわからない内容だったと思います。 ただ、発表中でもお話したとおり、この内容は「複数HLSを同期再生する時の手助けになる資料」を目指して作りました。 もしどこかの誰かが、 EXT-X-PROGRAM-DATE-TIME タグを利用して、複数のHLS動画を同期再生したくなった際には、とても役に立つと思います。 いったいなにがどうなると、そんな状況に陥るのか全く分かりませんが。むしろ陥らないほうが幸せなのでは。

ちなみに、たくさんの人に聞きに来て頂いたので「すごい!複数HLS同期再生仲間がいるかもしれない!」と内心すごいわくわくしていたんですが、 誰一人としてHLS同期仲間に出会えませんでした。かなしい。ただ、AVPlayer仲間には出会え、動画再生の知見を交換したり、 「AndroidのExoPlayer便利じゃね?」という話をしたりできたのは、とても楽しかったです。

感情

去年参加した時もとても楽しかったんですが、スピーカーではなかったので、どうにも物足りない気持ちでした。 「来年は絶対にCfP大量に出してスピーカーで参加するぞ!」という強い意気込みをもち、 今年は全部で4つのCfPを出し、無事にスピーカーとして参加することが出来ました。

これは不思議なんですが、スピーカーとして参加すると「圧倒的当事者意識」が高まることに気づきました。 「せっかく採択してもらったし、ちゃんと発表準備しないとな」、「わざわざ聞きに来てもらうのだから、役に立つ情報をわかりやすく伝えないとな」と思い、何度も資料の構成を練り直しました。 iOSDCという参加するだけで楽しいイベントを、スピーカーという立場から盛り上げるためにはどうすればいいだろうかと考えながら作った資料なので、だれかが楽しんでもらえていると嬉しいです。

ちなみに、会社として用意したノベルティである「LGTM扇子」や「iOSできますよ。Tシャツ」も、そんな「参加者の人達の楽しみをもっと増やしたい」という気持ちから生まれたものだったりします。(もちろん自分一人で考えたわけではないですが)

さいごに

これが本当によくて、あまりの良さにちょっとウルッとしていました。

www.youtube.com

「俺たち、ビジネスでは敵かもしれないけど、審査やXcodeやAutoLayoutに苦しめられながらも、iOSアプリ開発を楽しむ仲間じゃん?」

#iOSDC Japan 2018 で『複数のライブ映像を同期再生するのが大変だったので知見をお伝えします』というお話をします。

f:id:FromAtom:20180830152940p:plain

今年もiOSDC Japanの季節がやってきましたね!ありがたいことに15分トークが採択されたので、登壇します!

プロポーザルには、

ライブ配信サービスが流行っている中、WWDC2017で EXT-X-PROGRAM-DATE-TIME タグのサポートが発表されました。これにより、複数のAVPlayerに表示されるライブ映像を同期することが可能になりました。

このトークでは、

・複数のライブ映像をズレなく同期再生するノウハウ

・ハマりどころ

・設計のコツ

を実際にプロダクション環境で運用した経験をもとにお話します。

https://fortee.jp/iosdc-japan-2018/proposal/ea691c74-27ed-45ca-9961-287004c2a2e0

と書きました。内容はほぼこの通りですが、完成した資料の目次としては

f:id:FromAtom:20180830151626p:plain

となりました。

このトークは将来「複数のライブ映像の同期再生を実装するだれか」が困った際に、手助けになることを目指してデザインしました。また、同期再生をする予定がない方でも、動画の再生やAVPlayerに興味がある方にも楽しめる内容になっているかと思います。

登壇は、

  • 日時:2018年08月31日(金) 10時50分〜
  • 会場:Track C

となっています。朝イチかつ1発目という非常に緊張する枠ですが、やっていきます。動画再生が得意・興味がある皆さんはぜひ聞きに来てください!そしていろいろお話しましょう!

iOSDC 楽しむぞ〜〜〜。

Swift 4.1 で多重配列を Flatten する。

環境

  • Swift version 4.1.2
  • Xcode 9.4.1

背景

※この記事は Sequence.flatMap を使って多重配列をFlattenしていた人向けの記事です。

Swift4.1になってから Sequence.flatMap がDeprecatedになり、かわりに mapcompactMap を使うようになりました。これらの使い方や導入経緯はこちらが詳しいです。

qiita.com

さて、 .flatMap { $0 } を使ってFlattenをしていた人からすると、多重配列をFlattenする方法がなくなってしまいました。

解決策

joined() を使えば良いです。

let multiDimensionalArray: [[Int]] = [[1, 2, 3], [4, 5], [6, 7, 8, 9]]
let joinedArray = Array(multiDimensionalArray.joined())
print(joinedArray) // => [1, 2, 3, 4, 5, 6, 7, 8, 9]

よかったですね。

備考

Swift3で flatten()joined() になっていたらしい。知らんかった。

github.com