文字っぽいの。

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

素振り

最近、「エンジニアリングスキルを高めるにはどうしたらよいか」といったニュアンスの話をすることが何回かありました。エンジニアリングスキルにはコーディング能力だけではなく、チームワークだったりプロダクト志向だったりステークホルダーの調整だったり、そういうのも必要になってきますが、この記事ではスキルセットの1つであるコーディング能力について話します。

さて、「コーディング能力をあげるには?」に対する回答は人それぞれあるとは思いますが、自分は「コードを書かない限り、コードを書けるようにはならない。」と考えています。コーディング能力上げたかったらコード書け。

=完=

だと困ってしまうと思うので、1つの手法である「素振り」について書きます。これは、自分にWebアプリ開発のやりかたを教えてくれた先輩の受け売りです。

素振りとは「コードを書いて、他人が使えるようにする」までを1セットとした行動の事です。他人も使えるというのが重要です。コードを書いてローカル環境においてあったり、GitHubのプライベートリポジトリにPushするだけでは素振りではありません。例えば、

  • Webアプリを作ってデプロイして誰でも使えるようにする
  • ライブラリを作ってGitHubに公開して誰でも使えるようにする
  • スマホアプリを作ってAppStoreやPlayStoreで公開する
  • Chrome拡張を作ってChromeウェブストアで公開する

などが素振りになります。自分のアイディアを技術の力で実現し、それをリリースして、ユーザーからのフィードバックを得るという一連の流れで1回の素振りが終わります。

リリースまでしないといけないので、素振りは大変です。しかし、他人が使えるという状態にすることで、

  • 自分のアイディア実現の為に必須な機能はどれか
  • あったほうが便利だけど初期は無視していい機能はどれか
  • 無視して良いバグとダメなバグはどれか
  • ライブラリやIaaSなど開発効率をあげる仕組みはないか

などを考える必要がでてきます。というかこういうのを考えないと、「僕の考えた最強のサービス(ただし実装は脳内にある)」となって素振りを失敗します。

普段の仕事では自然と分業が進んでいくため、どうしても触れられる領域は狭くなります。一方素振りではチームメンバーが1人なので全部自分です。デザイン、実装、プライバシーポリシー・利用規約からマーケティングまで全部1人です。ライブラリを作る場合でもREADMEやドキュメントを整えたり、適切なライセンスを設定したり、CIの仕組みを整える必要があります。素振りをしていれば「やらざるを得ない」ため勝手に開発筋が鍛えられていきます。

最初のうちはリリースしても誰にも使われない事がほとんどでしょう。しかし素振りの中で学んだ技術や知識は確実に蓄積されていきます。そうやってコツコツと素振りを続けていくことで、一発当てる為の筋力がついてくると考えています。

ただし、野球やテニスなどスポーツにおける素振りと同じく「何も考えずにブンブンする」だけでは、少々筋力はつくかもしれませんがそれ以外の能力は向上しません。コーディングにおける素振りも同様で「使い慣れた技術で、作り慣れたものを作る」だけを続けていては効果が薄いでしょう。新しい知識を得てコードの書き方を変えてみたり、新しいライブラリを使ってみたり、工夫をしていくことが肝要です。

#iOSDC Japan 2022でLT登壇してきました!

帰ってきたオフライン開催!

今年のiOSDCはオフライン・オンライン両方のハイブリッド開催で、家で配信を見るのもよし、現地でトークを聞いたりブース出展を眺めるも良しの贅沢なカンファレンスになっていました。

3年ぶりに物理ブースが並んでいる様子をみると、感慨深くなりますね。

色んな人と久しぶりに物理会話できて楽しかったです。

LT登壇

めでたいことに今年も採択していただいたので、LTしてきました。なんとこれで5年連続登壇です!

このLTでは時間の都合上カットしたのですが、最も推奨されるサポートiOSバージョンの決め方は、

最新メジャーバージョン2つをサポートする。ただし、最新メジャーバージョンは出てすぐが不安定なので、マイナーバージョンが2くらいになって安定してきたら考える。

です。しかし、この決定方法はエンジニアにある程度 "力(りき)" がある場合にしか使えません。さらに、信頼関係が築けていない状態で「最新メジャーバージョン2つ!ビジネスの都合とか知らん!界隈ではこれが普通!」とゴリ押しをしては、エンジニア VS ビジネスという対立構造を生んでしまいます。

そういった悲しみを発生させず、チーム全員で目線を揃えてちょうどよい基準を決められるように、資料をまとめてみました。

資料のデザイン

最近流行っているリキッド系にしました。iOS 16もこんな感じ(もうちょっと3Dだけど)だし、なによりSplatoon3のバンカラジオもこんな感じ。 バンカラジオで使われてる画面転換も作りたかったんですが、Keynoteでやるには難しすぎて諦めました。

個人的には、ここのトランジションがかわいく作れたので満足しています。

ラーメン

昔のiOSDCではランチも提供されて(しかも旨い!)いたのですが、今年はご時世もありお水とジュースのみが提供されていました。 お昼は各自でなんとかするのですが、せっかく高田馬場という遠くの街に来たので普段行けないラーメン屋を巡っていました。

ピコピコポン

tabelog.com

ふく流らーめん 轍

tabelog.com

美味しかったです 🤤

まとめ

久しぶりの物理登壇はとても緊張しましたが、やっぱりとても良いものでした。感染症対策でランチも懇親会も無限コーヒーもなかったですが、やっぱり人間を対面して話せるというのはとても楽しかったです。

また、LT資料の準備を終わらせたいときに、オンラインでも配信があったので、聞きながら準備ができてとても助かりました。

来年もハイブリッド開催してもらえると嬉しいと思いつつ、スタッフさんに求められる機材操作能力がくっそ高そうで大変そうだなぁとも思いました。大変お疲れさまです……!

自分個人としては、5年連続登壇が嬉しかったのでここらで一段落したい気持ちもありつつ、来年も登壇できたらいいなぁという気持ちもあります。CM作成もやりたいね。

それでは皆様、また来年お会いしましょう。

SwiftでURLやファイル拡張子から画像か動画か判別する。

やりたいこと

WKWebViewの

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void)

でタップされたリンクが画像ならAという処理、動画ならBという処理をしたいという事がある。WebViewだけでなく、localのメディアに対するfile://path/to/fileというURLを取得した場合も同様に知りたいし、なんならファイル名だけ(hoge.png)だけからも知りたい。

よくある解決方法

ざっくり調べると拡張子をリストしておいて、それと合致するか調べる方法がよく出てくる。

let imageUrl = URL(string: "file://path/to/file.jpg")!

let imageExtensions = ["png", "jpg", "gif"]
if imageExtensions.contains(imageUrl.pathExtension) {
    print("これは画像です")
}

対応している拡張子が少ない場合や限定したい場合はこの方法が良い。

ただし、上記のコードでは .jpg はマッチするが .jpeg はマッチしないみたいな問題があるのと、「とりあえず画像なら処理Aをしたい」という場合には網羅するのが大変になってくる。

解決方法

UTType を利用すれば良い。

developer.apple.com

import UniformTypeIdentifiers

let movieUrl = URL(string: "file://path/to/file.mov")!

print(UTType(filenameExtension: movieUrl.pathExtension)!.conforms(to: .movie)) // => true
print(UTType(filenameExtension: movieUrl.pathExtension)!.conforms(to: .image)) // => false

let imageUrl = URL(string: "file://path/to/file.jpg")!

print(UTType(filenameExtension: imageUrl.pathExtension)!.conforms(to: .movie)) // => false
print(UTType(filenameExtension: imageUrl.pathExtension)!.conforms(to: .image)) // => true

ちなみに .video というのもあるが、そちらは音声が含まれない動画になる。作る機能によっては .video のみであったり .movie, .video の両方がマッチするように条件文を書く必要がある。

シミュレーターのPHPickerViewControllerでHEIC形式の画像を読み込めない問題の解決方法

環境

起こること

PHPickerViewControllerを利用して画像を取得する時、インターネットに転がってるコードを参考に実装していくとこんな感じになる。

func showPicker() {
    var configuration = PHPickerConfiguration()
    configuration.filter = .any(of: [.images])

    let picker = PHPickerViewController(configuration: configuration)
    picker.delegate = self
    present(picker, animated: true)
}

func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
    picker.dismiss(animated: true)

    guard let itemProvider = results.first?.itemProvider else {
        return
    }

    let typeChecked = itemProvider.registeredTypeIdentifiers.map { itemProvider.hasItemConformingToTypeIdentifier($0) }
    guard !typeChecked.contains(false) else {
        return
    }
    guard itemProvider.canLoadObject(ofClass: UIImage.self) else {
        return
    }

    itemProvider.loadObject(ofClass: UIImage.self) { object, error in
        let image = object as? UIImage

        DispatchQueue.main.async {
            self.imageView.image = image
        }
    }
}

showPicker() を呼べばPHPickerViewControllerが表示されて、画像を1枚選択できる。その後はコードに書いてある通り UIImage に変換して UIImageView にセットしている。

このコードだとほとんど上手くいくが、シミュレーターで試してみると『ピンク(紫)の花の画像』だけうまく取得できない。

エラーとしてはこんな感じのものが出る

[claims] Upload preparation for claim CA6415EA-8DE0-45D8-A1CD-B090459A2EC7 completed with error: Error Domain=NSCocoaErrorDomain Code=260 "The file “version=1&uuid=CC95F08C-88C3-4012-9D6D-64A413D254B3&mode=current.jpeg” couldn’t be opened because there is no such file."

調査

調べてみると、この画像だけ registeredTypeIdentifiers に "public.heic" も含まれていることがわかる。

print(itemProvider.registeredTypeIdentifiers) // => ["public.jpeg", "public.heic"]

シミュレーターに元々入っている他の画像は ["public.jpeg"] なので、HEICタイプの画像かどうかが影響していそう。

試しにHEIC形式の写真をiPhoneで撮影してシミュレーターに転送してみたところ、こちらも開けなかった。

どうやらHEIC形式の画像の場合、loadObjectが利用できなさそうなので、別の方法でデータを取得する必要がありそう。

対応方法

結論としては、

  • preferredAssetRepresentationMode.current に指定する
  • loadObject ではなく loadFileRepresentation を使う

ことでHEICもJPEGも同じように取得できた。それに対応したコードは下記。

func showPicker() {
    var configuration = PHPickerConfiguration()
    configuration.filter = .any(of: [.images])
    configuration.preferredAssetRepresentationMode = .current // この設定も追加が必要

    let picker = PHPickerViewController(configuration: configuration)
    picker.delegate = self
    present(picker, animated: true)
}

func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
    picker.dismiss(animated: true)

    guard let itemProvider = results.first?.itemProvider else {
        return
    }

    let typeChecked = itemProvider.registeredTypeIdentifiers.map { itemProvider.hasItemConformingToTypeIdentifier($0) }
    guard !typeChecked.contains(false) else {
        return
    }

    itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.image.identifier) { (url, error) in
        guard let url = url else {
            return
        }
        guard let imageData = try? Data(contentsOf: url) else {
            return
        }

        DispatchQueue.main.async {
            self.imageView.image = UIImage(data: imageData)
        }
    }
}

備考

  • 表題通り実機だと問題なく動く
  • シミュレーターでもうまく読み込めるHEICとそうでないのがある
    • 様子を見ていると mode=current.heic から mode=current.jpeg を生成しているが、これが上手く動かないことがあるっぽい

スネてる暇はないんだよ。

力を入れて書いたブログに反応がなかった時、これは賢いと思った設計変更が受け入れられなかった時、絶対にバズると思った企画が通らなかった時、これは通ると思ったプロポーザルが不採択になった時、便利なライブラリを作ったけどスターがつかない時、絶対ユーザーに喜ばれると思う機能開発が許可されない時。

無能感や気恥ずかしさから、ついつい僕たちは他人のせいにして「この素晴らしさが分からないなんて、なんて愚かな人たちだろう」とスネてしまう。

そんな暇はないんだ。本当に良いと信じるものならば、分かってもらう努力を続けないといけない。本当になにかを良くしたいなら、スネてる暇はないんだよ。

Swiftでファイル名からMIMEタイプを生成する。

やりたいこと

ファイル名からMIMEタイプを生成したい時がある。例えば、

  • "image.png" -> image/png
  • "movie.mov" -> video/quicktime

という感じ。

実装方法

Uniform Type Identifiers を利用する。そのため、iOS 14以降じゃないと使えない。

上述したPNG画像の場合はこんな感じ。

import UniformTypeIdentifiers

let url = URL(string: "file://path/to/image.png")!
let mimeType = UTType(filenameExtension: url.pathExtension)!.preferredMIMEType!

print(mimeType) // => "image/png"

動画も同じ感じで使える。

import UniformTypeIdentifiers

let url = URL(string: "file://path/to/movie.mov")!
let mimeType = UTType(filenameExtension: url.pathExtension)!.preferredMIMEType!

print(mimeType) // => "video/quicktime"

URLではなく、NSStringでも同様のことができる。Stringはそのままだと使えないので、一旦NSStringに変換することになる。

import UniformTypeIdentifiers

let nsString = NSString(string: "video.mov")
let mimeType = UTType(filenameExtension: nsString.pathExtension)!.preferredMIMEType!

print(mimeType) // => "video/quicktime"

備考

当然だが、これはファイル名(の拡張子)からMIMEタイプに変換している。実際にファイルの中身を検査している訳ではないため、拡張子がついていなかったり拡張子が信用できない場合には利用できない。

「僕の考えた最強のデスク環境」をアップデートした。

2年前にデスク環境をがっちり整えた。

fromatom.hatenablog.com

そこから時は経ち、書斎の棚を処分したり新しくWindowsPCを購入したりと環境も変わってきたので、アップデートをした。

この記事では、この状態になるまでに行った作業や、使った製品を紹介していく。

デスクのリメイク

前まで使っていたデスクはこんな感じの白いものだった。

この白い色にだんだん飽きてきたので、暗い茶色のデスクが欲しくなった。KANADEMONOだったり、PREDUCTSだったりと世の中にはおしゃれなデスクが様々あるけれどお値段が高すぎるのと「エンジニアなら既製品買わずにDIYできるやろ」とも思ったのでリメイクすることにした。

使った商品はこれ

item.rakuten.co.jp

いわゆるリメイクシートというやつで、この商品はシールになっているので接着剤が必要なくて便利。デスクの大きさにもよるけれど、自分のデスクサイズだと5,000円程度で購入できた。きれいに貼り付けるのは結構大変だけど、机を買い換えるより全然安いので頑張る。

できた。案外いい感じ。足が白いのだけ微妙だけど、流石に金属の塗装まで手を出すと大仕事になるのでやめておいた。

ちなみに、リメイクシートを買う時には、先にサンプルを購入するのを強くおすすめする。

item.rakuten.co.jp

ネットのサンプル画像では色味も表面のテカリ具合もテクスチャ感も全然分からないので、絶対にサンプルは買おう。

デスク天板裏

天板にリメイクシートを貼るために、デスクにくっついたすべてを取っ払ったので、天板裏のケーブル関係も更新することにした。前回の記事では、超強力両面テープでケーブルボックスを貼り付けていたけど、それは取り外した。

できたものはこんな感じ。

まず左奥にあるのは前回の記事で設置したMac mini用のホルダー。M1チップのMac miniになったので発熱もなくて天板裏でも安心して使えるようになった。今回は特に移動もさせず引き続き使っていくことにした。

次に右奥にあるのは、天板裏のケーブル収納では有名なサンワサプライのケーブルトレー。

ネジ止め方式のものを購入した。そのまま木ねじを止めても良さそうだったけど、鬼目ナットを利用して固定している。結構太い穴をあけないといけないので大変だった。

次に右前にある白いやつはこれ

ティッシュボックスって、手が届くところに置きたいけど地味にでかいし、ダサいし、中身が切れると交換しないとだしで置き場所に困っていたけれど、このテーブル下ラックだとこんな感じで入れられて便利。

これは付属のネジでそのまま固定している。当然だけど、この机のように梁があるタイプだと、ちゃんとティッシュボックスが出し入れできる位置か確認してから取り付けないといけない。

ティッシュボックスがない場所には、引き出しがある。

サイズが丁度いいトレーが全く見つからなかったので、ダイソーのトレーにダイソーのフックを逆さまにつけて取っ手にしたハンドメイド。ダサいけど、机の下は見えないからOK。ちなみにそのままだと滑りが良すぎて使いにくいので、マグネットシートを貼って使っている。

あれこれ配線した後はこんな感じになる。床にケーブルが一切なくなって最高な状態。掃除機かけ放題。

ケーブルは速攻でケーブルトレー内に入るようにしている。ケーブルトレー内にはこの電源タップがドカンと入っていて、壁のコンセントから直接つながるようにしている。口数も多いしコンセント部分は180°回転するようになっているし、雷ガードもついているので大変便利。

ケーブルトレーの外に出ているケーブルはこれらの商品でまとめている。

新しいガジェットがやって来たり、配置を変えた時にはケーブルの配線を直す必要がでてくる。その時にとにかく楽に雑に整理がしやすいことをモットーにしている。ケーブル用のチューブでまとめると確かにかっこよくはなるけど、「巻き直すの面倒だからとりあえずこのままで」といってはみ出したケーブルが破滅を呼ぶのが分かりきっているので使わない。また、天板裏にこういったフックをつけたこともあるが……。

これはケーブルの総数が多いと全く役に立たなくなる。こういうフックは元々ケーブルが少なくてシンプルな構成の人だけが使えるものだと気づいて全部外した。クランプで全部まとめりゃええんや。

デスク上部

デスク上のここらへんに写っているものの紹介。

まずディスプレイだけど、下の大きいのはこれ

いまは11万円程度で売っているけど、一時期クーポンの設定を失敗したのか67,000円弱で買える時があって、その時に購入した。でっかいしキレイで便利。問題はUSB Type-Cでの接続はサポートしていないこと。そのため、MacとはDisplayPortで接続している。USBのハブ機能はついているので、こんな感じでディスプレイ裏にUSBハブをくっつけて使っている。

このUSBハブは

  • Webカメラ
  • LED照明
  • マウスレシーバー
  • USBマイク
  • iPad
  • キーボード

と色々がデバイスがつながるため、セルフパワー式のものを使っている。

次に上のディスプレイはこれ。

だいたいYouTubeが流れている。基本的にはメインディスプレイを横に2分割して使えば事足りるけど、たまに横幅を要求されることがあるので、そういう時にはメインとサブを使えて便利。

ディスプレイアームはメインはこれ

flexispot.jp

サブディスプレイとiPad

という長いポール1本に

を2つ通して使っている。アームだけで購入しないとポールが3本になるので注意。ちなみにiPadはこういうVESAに対応したホルダーがあるのでそれを使っている。

マイクとマイクスタンドはこれ。マイクスタンドの見た目が良いやつがなくて困っていたけど、コレが一番良かった。

スピーカーはもう売っていないけど、Bose Companion 3 Series II systemというやつ。手元にコントローラーが持ってこれるんだけど、コレが非常に便利。壊れないでほしい。

キーボードはHHKB Pro2を2台使っていて、マウスはロジクールのMX ANYWHERE 3。この組み合わせが一番使いやすいと思います。

キーボードの下に敷いているマットはGrovemadeというメーカーのもの。輸入になるので送料が高い。

grovemade.com

こんなマット要るんかいって思うけど、このマットごと奥に滑らせるとキーボードとリストレストとマウスを全部一気に机奥に移動させられるのが便利。机でなにか書き物をしたり、ご飯を食べたり、WindowsPC用のキーボードやマウスを出したりと、そういった場面で非常に役に立つ。

キーボードの手前にあるブーメランみたいなものはリストレスト

deltahub.io

これがなかなか便利で、リストレストに手をおいたまま手を滑らせて、キーボードとマウスを使い続けられる。よくあるデスク構成だとキーボード側にだけリストレストがあるため、マウスとキーボードを行き来する時に「よっこいしょ」と手を上げ下げする必要がある。このDeltaHubのリストレストなら、手を横に滑らせるだけで良いのでとても楽。ただ地味に高い。

デスク上にある小さい時計はこれ。可愛くて買ったけど、カチカチ音が結構うるさい。

七味は根元 八幡屋礒五郎。一番美味しい七味。

サブデスク(PCラック)

つぎにここらへんのもの。

サブデスク的に使っているけど、これはPCラックになっている。自分が買った商品はすでに売らなくなってしまったけど、こういったラックを購入した。

中にはWindowsPCとPS4と有線LANのハブが入れてある。

上においてあるのは、キングジムのペギー

今の所メガネスタンドとしての役割しかなくてかわいそう。

あとは、AnkerのQii充電スタンド

CO2モニターがおいてある。

このラックの奥側には、このケーブルホルダーが貼り付けてある。

基本的に充電ケーブルはここにすべてくっつけてあって、必要になったら引っ張り出して使う運用にしている。PCラックが充電用テーブル的に使われてる感じ。

壁掛け

ここらへんの壁にかかっている簡単な収納。

正面から見るとこんな感じになっている。

これは無印良品の壁につけられる家具棚を改造したもの。

www.muji.com

天面には、キーボードが前に落ちないようにホームセンターで買った黒い取っ手をネジ止めしている。それだけだと、キーボードがゴリッとなって傷ついてしまうので、ダイソータブレットスタンドを使っている。

realsound.jp

流石にでかい地震だとキーボードは落ちてくるだろうけど、まぁこれが落ちるレベルの地震なら命のほうが大事なので良いでしょう。

前面には、これまたホームセンターで買った黒いメタルフックと、Amazonで買ったヘッドホンフックをネジ止めしている。

ヘッドホンフック、1個単位で変える良い見た目のやつがないのでちょっと困った。6個もいらん。

ちなみに、どのパーツも付属するネジだと長くて貫通してしまうので、予めホームセンターで丁度よい長さのネジを購入しておいた。

音周り

音周りはこういう構成にしている。この構成だと常にスピーカーからは音を出しつつ、BTヘッドホンからも音が出せる。スピーカーのミュートが手元で簡単にできるからできる構成という感じがする。

トランスミッターはAnkerのものを使っていて、机の下のケーブルトレー内で常に充電状態にされている。

なんでこんな面倒なことをしているかというと、Bluetoothヘッドホンをつないだ時にヘッドホン側のマイクを使う設定に切り替えられてしまうのが不便すぎるから。マイクは常にYetiのものになってほしいので。副作用として便利だったのは、MTG中にヘッドホンの充電が死にかけたり、最初はスピーカーで聞いてたけどヘッドホンに切り替えるかって時に、音声を聞き漏らすことなく移行できる点。

まとめ

机の色を変えるには買い替えしかないと思っていたけど、リメイクシートでなかなかいい感じに安くできてよかった。デスク下の構成も一気に快適になったので、満足している。