2024/01/19 (31)

id:yutailang0119 の通り、2024年1月19日に誕生日を迎えました。
片手で数えられるラストイヤー。

昨年 yutailang0119.hatenablog.com

今年こそ30回目の誕生日

仕事

昨年は会社、チームのいろいろがあって、とにかく働いた1年でした。
ちゃんと働いてるのかと思われていたかもしれないけど、昨年は社会人生活で1、2を争う働きっぷり。
現在進行形で働きまくっている。

助けてくれ!
hatena.co.jp

1年前の査定で、社内グレードがS4になりました。
入社した時は、遥か雲の上の領域と思っていたけど、なんとかやっていけています (評価してもらえています) 。
はてな社内だと、IC的な扱いのポジションに収まっています。
はてなのグレード制 (2024/01) 参考: 株式会社はてな エンジニア採用資料 / Engineers Recruitment - Speaker Deck

生活

三十代をあまり感じない

大阪に引っ越しました

昨年書いたように、大阪に引っ越しました。
京都オフィスには1時間くらいかかるので、2週間に一度程度、なんとなく行くという感じになっています。

マンションには管理会社が常駐していて荷物の発送を代わってくれたり、ディスポーザーがついていたりとかなり快適。

趣味

ビールがうまい、箕面ビールの桃ヴァイツェン4種制覇できた。
桃ヴァイツェン白鳳

ライブ行ったり。

最近は仮面ライダーにハマっています。

猫との生活

Visionフレームワークを活用した猫のポーズ検出 #はてなエンジニアアドベントカレンダー2023 - がんばってなんか書く でご紹介の通り、オスの茶虎 "ビビ" がやってきました。

かわいい、とにかくかわいい

2024年初ビビ
2024年初ビビ

写真集 ビビ写真で一番のお気に入り 2023年ラストビビ WWDC24を見ていて、早よ寝ろの顔をするビビ ちゅ〜るを食べる時は野生の顔をする 野生を忘れて寝ているビビ

try! Swift Tokyo 2024

仕事が忙しすぎて、他メンバーに頼り切りなのですが、帰ってきたtry! Swift Tokyoのオーガナイザーのひとりをやっています。
2024/3/22 (金) ~ 24 (日) です、迷っている方は今すぐチケットを買いましょう!

tryswift.jp

最後に

みんな人間よりも、猫の方が支援したいでしょ。

www.amazon.jp

Visionフレームワークを活用した猫のポーズ検出 #はてなエンジニアアドベントカレンダー2023

はてなエンジニアアドベントカレンダー2023 1日目の記事です。
アドベントカレンダー初日は id:yutailang0119 が担当します。

猫と生活しています

2022/01/19 (30) - がんばってなんか書く の引っ越しに合わせて、猫がやってきました。
オスの茶虎 "ビビ" です。

ビビ

今回は、WWDC 23でVisionフレームワークに追加されたAnimal Body Poseの検証を、ビビに手伝ってもらいます。

Animal Body Pose

Detecting animal body poses with Vision | Apple Developer Documentation のサンプルアプリを使って撮影します。
実行環境は、iPad mini (6th generation) iPadOS 17.1.1です。

ビビのポーズ検出

スクリーンショットでの紹介ですが、サンプルアプリではVideoのインプットを、リアルタイムに検出します。
公開されているサンプルで、おもしろい写真が撮れることがわかりました。

VNDetectAnimalBodyPoseRequest

Animal Body Poseには、ポーズ検出を行うVNDetectAnimalBodyPoseRequest を使います。
関節をはじめとする25のジョイントを検出できます。
現在は、猫と犬をサポートしています。

より詳しくは、WWDC23のビデオを参照してください。

developer.apple.com

ちなみに、人間に対してはVNDetectHumanBodyPoseRequestを使用します。
Detecting Human Body Poses in Images | Apple Developer Documentation

VNRecognizeAnimalsRequest

VNDetectAnimalBodyPoseRequest は2023年OSがサポートするAPIですが、以前のOSでも使用できるAPIがあります。
Visionフレームワークには、VNRecognizeAnimalsRequestがあり、動物の特定ができます。
サンプルアプリを変更して、猫自身の特定も行ってみました。

ビビ自身の検出

boundingBoxの扱いには難があります*1が、I/Oの処理はVNDetectAnimalBodyPoseRequestと同様です。
VNImageRequestHandler.perform(_:)には、複数のVNRequestを実行可能なので、VNDetectAnimalBodyPoseRequestVNRecognizeAnimalsRequestを同時に処理できます。

さいごに

画像処理やMLに踏み込む必要なく、Visionフレームワークを活用すると、猫自体の検出とポーズ検出を行なうことができました。
猫のポーズに合わせて写真を撮るアプリなら、簡単に作れそうです。
Apple Vision Proのために、Visionフレームワークの練習に励んでいきましょう!

明日のはてなエンジニアアドベントカレンダー2023担当は id:pokutuna です!

ビビへのファンレターもお待ちしています!!!

おまけ

オフショット集

WEB+DB PRESSの思い出 #wdpress #wdpress_party

WEB+DB PRESSの思い出 - Lento con forza に感化されて、自分も書こうと思います。
WEB+DB PRESSがまさか休刊になると思っていなくて、本当にびっくりしました。

自分がソフトウェアエンジニアを志向したのは大学3 - 4年生の時で、プログラミング教本以外の雑誌という存在に出会ったのは、社会人になってから。
ありがたいことに、WEB+DB PRESSにはイベントレポートを1回、特集を3回寄稿する機会をもらえました。

どれも縁があったと感じられるものです。

builderscon 2017 tokyoイベントレポート

yutailang0119.hatenablog.com

builderscon 2017 tokyo内で行なった「WEB+DB PRESS 100号記念特別企画」の流れで、イベントレポートを担当させてもらいました。
鮮明には覚えていないけど、若者ムーブを発揮して、企画担当に手を挙げたんだと思う。
ここまでWEB+DB PRESSに深い関わりがなかった自分だったけど、読者代表として同じステージに立ち、 WEB+DB PRESS編集長の@inaoの情熱に触れる機会でした。
この出会いが後々に繋がっていくので、目の前のチャンスには躊躇せず飛び込むべきことがわかる。

先日のiOSDC Japan 2023で、@lestrratさんと久しぶりにお会いして、少しお話できましたが、まだまだ伝えきれない感謝があります。

WEB+DB PRESS Vol.116 特集1 「はじめてのトラブルシューティング

yutailang0119.hatenablog.com

builderscon🐟を通じて、より親しくなった id:Soudai さんに誘っていただいて、モバイルアプリの章を担当しました。
忘れもしなくて、会社の忘年会後の二次会中にDMがやってきて、これは夢か?と思った。
初めての特集記事で勝手が分からずに配分に苦労したり、Android領域の解説に悩んだりしたので、無事に入稿された時には灰になっていたと思う。

WEB+DB PRESS Vol.121 特集2 「iOS 14最前線」

yutailang0119.hatenablog.com

この回は自分から@inaoさんにアプローチして、iOSのバージョンアップに対する特集を担当させてもらいました。
今度は自分が特集幹事。
メンバーは同僚の id:cockscomb さん、id:kouki_dan さんにお願いしました。
はてなプレゼンツにできたのは、鼻が高い出来事です。

WEB+DB PRESS Vol.132 特集2 「iOS 16最前線」

yutailang0119.hatenablog.com

iOS 14特集から2年、またどうですかということになり、iOS 16も担当できることになりました。
メンバーは前回と同じく id:cockscomb id:kouki_dan id:yutailang0119
この回も大変だったけど、自分が経験を積み、メンバーも経験者ということで、安定感がある布陣でした。
Swift新機能の解説を浅すぎず、ニッチすぎず、ちょうどよいバランスでできたと思っていて、満足度が高い出来になりました。

まさか、これがWEB+DB PRESS最後のiOS特集になるとは思わなかった...

これ以外にも

自身が直接執筆に携わったわけではないですが...
編集部からPython特集企画の相談をもらい、自分よりも適任がいるだろうとPyCon JPから @rhoboro を中心として、同世代でPythonを解説できそうな人選に協力させてもらいました。
レビューもさせてもらった。

WEB+DB PRESS Vol.104|技術評論社

WEB+DB PRESS Vol.104 特集1「[モダンなコードをギュッと凝縮!]イマドキPython入門 文法,機械学習,Web開発を一気に学ぼう」

その後、書籍の執筆にも繋がったと伺っています。

www.rhoboro.com

id:cockscomb のGraphQL特集もよかったね。

cockscomb.hatenablog.com

おわりに

思い返せば、WEB+DB PRESSとは著者として関わることが深くなり、自分が誇れる仕事の一つになっています。
商業誌でソフトウェアについて文章を書いて、お金を稼ぐという経験ができたのは、本当によかった。
自分をソフトウェアエンジニアとして育ててくれた物の1つなので、WEB+DB PRESS休刊は時代の変化を感じる出来事でした。
最終号Vol.136の次号予告の「ご愛読ありがとうございました。」には、本当に泣きそうになってしまった。

最後に、WEB+DB PRESS編集長の@inaoさんへの感謝。
inaoさんには本当によくしていただいて、任せていただいて、感謝しかありません。
執筆の場での編集者としての関わりだけでなく、イベントでお会いした時には気にかけてくださって、ニコニコ話を聞いてくださって、楽しい思い出ばかりです。
ソフトウェアエンジニアとも、IT企業の他職種とも違った立ち位置で、自分にはとても稀有な存在です。
inaoさんの益々のご活躍を、心からお祈り申し上げます。

この記事はconnpass.comに向かう新幹線で書きました。
所属企業のはてなには、iOSDCに続いて出張として支援もらいました。ありがとうございます!
参加者の皆さま、会場でお会いしましょう!
会場で泣く準備はバッチリです。

最終号の物理本を会場に持って行くので、参加者全員のサイン本にしたいと思っています!
よろしくお願いします!

2023/01/19 (30)

id:yutailang0119 の通り、2023年1月19日に誕生日を迎えました。
ついに三十代に突入。

昨年 yutailang0119.hatenablog.com

ところで、数え年じゃないから、迎えた誕生日は 年齢 - 1 では🤔

仕事

実は去年の誕生日直後、2022年2月1日付で「マンガアプリチーム」に異動しました。
前回ブログを書いた頃には、もう異動が決まっていた。

現在は「GigaViewer for Apps」を作っていて、ほぼiOS Developerとしての稼働になっています。
id:ikesyoid:kouki_dan を始めとして、大人数になったチームで働いています。

CM

hatena.co.jp

生活

三十代怖い

大阪に引っ越します

京都から大阪に引っ越しをします。
はてなから転職するわけではありません。
京都を離れるのは寂しい。

大阪市内へなので、大移動ではないですが。
永住と思って京都に来たわけではなかったけど、関西歴も長くなってきた...

ゲーム

スプラトゥーン3とポケモンSVを行ったり来たり。
最近、Nintendo Switch有機ELモデル)をゲットしました。
スプラトゥーン3モデル、かわいい。

try! Swift Tokyo Meetup

明後日 1/21 (土) 開催の try! Swift Tokyo Meetup のオーガナイザーをやっています!
COVID-19の影響もだいぶ緩和されてきたので、準備運動からという感じです。
今年はWWDCも行きたい。

明日から東京出張。
現地で参加される方、お話ししましょう!

最後に

いつものあれです。
ご支援お待ちしております。
引っ越しもあるので、大きいものや冷凍のものは引っ越し後に...

www.amazon.jp

Programmatic NavigationStack 備考録

これははてなエンジニアアドベントカレンダー2022 37日目の記事です。
2回目の登場 id:yutailang0119 です、おはこんハロチャオ~!
昨日は id:hogashiaタグで#topにリンクするとページ先頭にスクロールするのは仕様 - hogashi.* でした。

冬休みはあつ森で年越ししながら、SwiftUIの2022年OS対応をしていた

Migrating to new navigation typesNavigationStack を読んで、"programmatic navigation" のパターンをイメージしづらかったので、サンプルを用意しながら動きを見てみた備考録。

Enviromnent

  • Apple Swift version 5.7.2 (swiftlang-5.7.2.135.5 clang-1400.0.29.51)
  • Xcode Version 14.2 (14C18)
  • iOS 16.2

単一の型に対応した画面遷移

 まずはPark型を単独で扱う画面遷移パターン。

NavigationStackに記載されるコード断片の裏側を推測して進めていく。

対応する型 -- Park

 SwiftUIでナビゲーションに対応する型には Hashable にconformさせる。
Identifiable にもするのは、List で扱いやすくするため。

struct Park: Identifiable, Hashable {
    var id: String {
        name
    }

    var name: String
}

画面遷移の実装

 画面遷移にはNavigationStackiOS 16新API init(_:value:)navigationDestination(for:destination:)を使う。

import SwiftUI

struct ContentView: View {
    var parks: [Park] = [
        Park(name: "Yosemite"),
        Park(name: "Sequoia"),
    ]

    var body: some View {
        NavigationStack {
            List(parks) { park in
                NavigationLink(park.name, value: park)
            }
        }
        .navigationDestination(for: Park.self) { park in
            ParkDetails(park: park)
        }
        .navigationTitle(String(describing: type(of: self)))
    }
}

struct ParkDetails: View {
    var park: Park

    var body: some View {
        Text(park.name)
            .navigationTitle(String(describing: type(of: self)))
    }
}

 これで Park のリストをタップすると ParkDetails の画面に遷移する。

複数の型に対応した画面遷移

 ここから、上記の例を拡張して、複数の型を扱うことを想定してみる。
Location を同一画面に表示して、LocatonDetails画面に遷移できるようにする。

struct ContentView: View {
    var parks: [Park] = [
        Park(name: "Yosemite"),
        Park(name: "Sequoia"),
    ]

    var locations: [Location] = [
        Location(name: "Cupertino"),
        Location(name: "San Jose"),
    ]

    var body: some View {
        NavigationStack {
            List {
                Section {
                    ForEach(parks) { park in
                        NavigationLink(park.name, value: park)
                    }
                }
                Section {
                    ForEach(locations) { location in
                        NavigationLink(location.name, value: location)
                    }
                }
            }
            .navigationDestination(for: Park.self) { park in
                ParkDetails(park: park)
            }
            .navigationDestination(for: Location.self) { location in
                Text(location.name)
            }
            .navigationTitle(String(describing: type(of: self)))
        }
    }
}

 navigationDestination(for:destination:) は、メソッドチェーンで複数連結し、異なる型の遷移先を定義できる。

 NavigationStack の "Manage navigation state" にあるように、init(path:root:) を使うと画面スタックをprogrammaticに操作できる。
例えば、一度にスタックを2つ重ねて遷移させることで、遷移元と遷移先画面の間に画面を挟んでスタックできる。

データの配列をBindして、ナビゲーション操作

struct ContentView: View {
    (省略)

    @State private var presentedParks: [Park] = []

    var body: some View {
        NavigationStack(path: $presentedParks) {
            List {(省略)}
            .navigationDestination(for: Park.self) { park in
                ParkDetails(park: park)
            }
            .navigationDestination(for: Location.self) { location in
                LocationDetails(location: location)
            }
            .navigationTitle(String(describing: type(of: self)))
            .toolbar {
                ToolbarItem {
                    Button {
                        presentedParks = [
                            Park(name: "Yosemite"),
                            Park(name: "Sequoia"),
                        ]
                        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                            presentedParks.removeAll()
                        }
                    } label: {
                        Text("Yosemite+Sequoia")
                    }
                }
            }
        }
    }
}

 この場合、"Yosemite+Sequoia"ボタンを押すと ParkDetails が2つ重なるスタックで画面遷移した後、全画面 dismiss して元の画面に戻った遷移になる。
presentedParks の配列を操作することで、画面スタックをコントロールすることができるということだ。

 しかし、残念なことに LocationDetailsへの遷移は動かなくなってしまう。

 ここで登場するのが、NavigationPath

A type-erased list of data representing the content of a navigation stack.

とある通り、型消去が使われている。

 NavigationPathNavigationStack.init(path:root:) にバインドして、ナビゲーションを操作する。

struct ContentView: View {
    (省略)

    @State private var path: NavigationPath = NavigationPath()

    var body: some View {
        NavigationStack(path: $path) {
            List {(省略)}
            .navigationDestination(for: Park.self) { park in
                ParkDetails(park: park)
            }
            .navigationDestination(for: Location.self) { location in
                LocationDetails(location: location)
            }
            .navigationTitle(String(describing: type(of: self)))
            .toolbar {
                ToolbarItem {
                    Button {
                        path.append(Park(name: "Yosemite"))
                        path.append(Location(name: "Cupertino"))
                        path.append(Park(name: "Sequoia"))
                    } label: {
                        Text("Yosemite+Cupertino+Sequoia")
                    }
                }
            }
        }
    }
}

 これで ParkDetailsLocationDetails の両方に遷移可能。
この場合、"Yosemite+Cupertino+Sequoia"ボタンを押すと ParkDetails > LocationDetails > ParkDetails の順にスタックで画面遷移をした状態になる。

データ型のBind vs NavigationPath

 一見すると NavigationPath で実装しておく方が、後々の拡張性が高そうに見える。
しかし、NavigationPath にも弱点はある。

 前述の通り、NavigationPath は型消去のため、詳細なデータに直接アクセスすることができない。
また、配列操作と違い、任意のindexにアクセスできないため、細やかなスタック操作の面では NavigationPath の分が悪そう。
要件に応じて、使い分けることになる。

まとめ

NavigationStack を用いて、programmaticに画面遷移を操作する方法を、備考録としてまとめた。
pathをencode/decodeすることで、画面のState Restorationも可能になる設計になっているので、非常にかしこい。

iOS 16のことをまとめて知りたいあなたへ

年末年始休暇が終わってしまって時間が足りない方に朗報。
WEB+DB PRESS Vol.132 特集2「iOS 16最前線」には、iOS 16についてのエッセンスが詰まっています!
WEB+DB PRESS Vol.132 特集2 「iOS 16最前線」に寄稿しました #wdpress - がんばってなんか書く

JSON ArrayをSwift.Rangeでdecodeすると?

Swift.RangeはCodableである、ではencodeフォーマットは? - がんばってなんか書く の続き

疑問

Swift.RangeJSON encodeした結果は [a, b] となることがわかった。
これはJSON Arrayを使って表現している。

では、[a, b, c] な要素数3以上のArrayに対して、RangeJSON decodeをかけるとどうなるだろうか?

環境

  • Apple Swift version 5.7.2 (swiftlang-5.7.2.135.5 clang-1400.0.29.51)
  • Xcode Version 14.2 (14C18)
    • Playground

実験

インプットするJSONはこちら

{
    "name":"0, 9, 10",
    "value":[
        0,
        9,
        10
    ]
}

JSONDecoder で、decodeする。

JSON内のArrayをRangeでdecodeする

import Foundation

let decoder = JSONDecoder()

let json = """
{
    "name":"0, 9, 10",
    "value":[
        0,
        9,
        10
    ]
}
"""

do {
    struct Content: Codable {
        var name: String
        var value: Range<Int>
    }

    do {
        let data = json.data(using: .utf8)
        let content = try decoder.decode(Content.self, from: data!)

        print(content)
    } catch {
        print(error)
    }
}

結果

Range(0..<9)

decoding errorとはならず、Arrayの第3要素目以降は無視して扱われることがわかった。

素数が足りない場合は...

ちなみに、要素数が足りない場合には、ちゃんとエラーになる

{
    "name":"0",
    "value":[
        0
    ]
}
valueNotFound(Swift.Int, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "value", intValue: nil), _JSONKey(stringValue: "Index 1", intValue: 1)], debugDescription: "Unkeyed container is at end.", underlyingError: nil))