$30 off During Our Annual Pro Sale. View Details »

ウーニャ、しってる。みんなふんいきでSwiftUIをつかってる。 / iosdc_japan_...

uhooi
September 10, 2022

ウーニャ、しってる。みんなふんいきでSwiftUIをつかってる。 / iosdc_japan_2022

iOSDC Japan 2022
https://iosdc.jp/2022/

## 参考リンク

### 採用

- CTOが訊く #6 iOSアプリエンジニアとしてDeNAにいる理由 | BLOG - DeNA Engineering
https://engineering.dena.com/blog/2022/09/cto_interview_06/
- 【事業横断】CTO直下ネイティブエンジニア(iOS/Android/Flutter) - 株式会社ディー・エヌ・エー
https://herp.careers/v1/denacareer/ejrQwvTcq2m8
- 【サービス開発】ソフトウェアエンジニア(オープン枠) - 株式会社ディー・エヌ・エー
https://herp.careers/v1/denacareer/4OqhgPs-GpBO
- DeNAのiOSアプリエンジニアと、ざっくばらんにお話ししませんか? - 株式会社ディー・エヌ・エーの中の人のカジュアル面談 - Meety
https://meety.net/matches/BMKFzZYOZYva

### 公式ドキュメント

- Label | Apple Developer Documentation
https://developer.apple.com/documentation/swiftui/label
- LabelStyle | Apple Developer Documentation
https://developer.apple.com/documentation/swiftui/labelstyle
- ViewModifier | Apple Developer Documentation
https://developer.apple.com/documentation/swiftui/viewmodifier

### その他

- View を拡張したい場合は原則として extension を使用し、状態保持が必要な場合のみ `ViewModifier` を実装する。 · Discussion #31 · YusukeHosonuma/Effective-SwiftUI
https://github.com/YusukeHosonuma/Effective-SwiftUI/discussions/31
- Atomic Design | Brad Frost
https://bradfrost.com/blog/post/atomic-web-design/

uhooi

September 10, 2022
Tweet

More Decks by uhooi

Other Decks in Programming

Transcript

  1. struct var var var some init : View { iconName:

    name: body: View { ( : ) { (iconName) . () . () . ( : , : ) (name) . (.title) . ( : . , : .leading) } . ( ) . ( (.systemBackground) . ( ) . ( : . ( : , : ), : , : , : ) ) } } MonsterListCellView String String HStack spacing 32 Image resizable scaledToFit frame width 68 height 68 Text font frame maxWidth infinity alignment padding 16 background Color cornerRadius 3 shadow color white 0 opacity 0.24 radius 1 x 0 y 1
  2. struct var var var some init : View { iconName:

    name: body: View { ( : ) { (iconName) . () . () . ( : , : ) (name) . (.title) . ( : . , : .leading) } . ( ) . ( (.systemBackground) . ( ) . ( : . ( : , : ), : , : , : ) ) } } MonsterListCellView String String HStack spacing 32 Image resizable scaledToFit frame width 68 height 68 Text font frame maxWidth infinity alignment padding 16 background Color cornerRadius 3 shadow color white 0 opacity 0.24 radius 1 x 0 y 1 Image + Text = Label .elevate()
  3. struct MonsterListCellView: View { var iconName: String var name: String

    var body: some View { } .padding(16) .background( Color(.systemBackground) .cornerRadius(3) .shadow( color: .init(white: 0, opacity: 0.24), radius: 1, x: 0, y: 1 ) - HStack(spacing: 32) { - Image(iconName) - .resizable() - .scaledToFit() - .frame(width: 68, height: 68) - Text(name) - .font(.title) - .frame(maxWidth: .infinity, alignment: .leading) + Label { + Text(name) + } icon: { + Image(iconName) + .resizable()
  4. struct MonsterListCellView: View { var iconName: String var name: String

    var body: some View { } .padding(16) .background( Color(.systemBackground) .cornerRadius(3) .shadow( color: .init(white: 0, opacity: 0.24), radius: 1, x: 0, y: 1 ) - HStack(spacing: 32) { - Image(iconName) - .resizable() - .scaledToFit() - .frame(width: 68, height: 68) - Text(name) - .font(.title) - .frame(maxWidth: .infinity, alignment: .leading) + Label { + Text(name) + } icon: { + Image(iconName) + .resizable()
  5. struct func -> some extension where static var init :

    LabelStyle { ( : Configuration) View { ( : ) { configuration.icon . () . ( : , : ) configuration.title . (.title) . ( : . , : .leading) } } } Self == MonsterListCellLabelStyle { monsterListCell: MonsterListCellLabelStyle { . () } } MonsterListCellLabelStyle makeBody configuration LabelStyle HStack spacing 32 scaledToFit frame width 68 height 68 font frame maxWidth infinity alignment MonsterListCellLabelStyle
  6. struct func -> some extension where static var init :

    LabelStyle { ( : Configuration) View { ( : ) { configuration.icon . () . ( : , : ) configuration.title . (.title) . ( : . , : .leading) } } } Self == MonsterListCellLabelStyle { monsterListCell: MonsterListCellLabelStyle { . () } } MonsterListCellLabelStyle makeBody configuration LabelStyle HStack spacing 32 scaledToFit frame width 68 height 68 font frame maxWidth infinity alignment makeBody() 内に
 表示する View を書く MonsterListCellLabelStyle
  7. struct func -> some extension where static var init :

    LabelStyle { ( : Configuration) View { ( : ) { configuration.icon . () . ( : , : ) configuration.title . (.title) . ( : . , : .leading) } } } Self == MonsterListCellLabelStyle { monsterListCell: MonsterListCellLabelStyle { . () } } MonsterListCellLabelStyle makeBody configuration LabelStyle HStack spacing 32 scaledToFit frame width 68 height 68 font frame maxWidth infinity alignment .labelStyle(MonsterListCellLabelStyle()) を
 .labelStyle(.monsterListCell) と書くための
 おまじない MonsterListCellLabelStyle
  8. struct MonsterListCellView: View { var iconName: String var name: String

    var body: some View { Label { Text(name) } icon: { Image(iconName) .resizable() } .padding(16) .background( Color(.systemBackground) .cornerRadius(3) .shadow( color: .init(white: 0, opacity: 0.24), radius: 1, x: 0, y: 1 ) ) } } + .labelStyle(.monsterListCell)
  9. struct MonsterListCellView: View { var iconName: String var name: String

    var body: some View { Label { Text(name) } icon: { Image(iconName) .resizable() } .padding(16) .background( Color(.systemBackground) .cornerRadius(3) .shadow( color: .init(white: 0, opacity: 0.24), radius: 1, x: 0, y: 1 ) ) } } + .labelStyle(.monsterListCell)
  10. struct var var var some : init : View {

    iconName: name: body: View { Label { (name) } icon { (iconName) . () } . (.monsterListCell) . ( ) . ( (.systemBackground) . ( ) . ( : . ( : , : ), : , : , : ) ) } } MonsterListCellView String String Text Image resizable labelStyle padding 16 background Color cornerRadius 3 shadow color white 0 opacity 0.24 radius 1 x 0 y 1
  11. struct var var var some : init : View {

    iconName: name: body: View { Label { (name) } icon { (iconName) . () } . (.monsterListCell) . ( ) . ( (.systemBackground) . ( ) . ( : . ( : , : ), : , : , : ) ) } } MonsterListCellView String String Text Image resizable labelStyle padding 16 background Color cornerRadius 3 shadow color white 0 opacity 0.24 radius 1 x 0 y 1 body 内の
 単一の View が
 少しボリューミー
  12. struct var var var some : init : View {

    iconName: name: body: View { Label { (name) } icon { (iconName) . () } . (.monsterListCell) . ( ) . ( (.systemBackground) . ( ) . ( : . ( : , : ), : , : , : ) ) } } MonsterListCellView String String Text Image resizable labelStyle padding 16 background Color cornerRadius 3 shadow color white 0 opacity 0.24 radius 1 x 0 y 1 どう切り出すのがいい? プロパティ? メソッド? 構造体?
  13. struct var var var some init private var some :

    : View { iconName: name: body: View { monsterLabel . ( ) . ( (.systemBackground) . ( ) . ( : . ( : , : ), : , : , : ) ) } monsterLabel: View { Label { (name) } icon { (iconName) . () } . (.monsterListCell) } } MonsterListCellView String String padding 16 background Color cornerRadius 3 shadow color white 0 opacity 0.24 radius 1 x 0 y 1 Text Image resizable labelStyle 単純な View だから
 プロパティに切り出すのが
 よさそう
  14. View の分割方法(変数) body: View { monsterLabel Label { (name) }

    icon { (iconName) . () }. (.monsterListCell) monsterLabel . ( ) . ( (.systemBackground) . ( ) . ( : . ( : , : ), : , : , : ) ) } var some let = : init Text Image resizable labelStyle padding 16 background Color cornerRadius 3 shadow color white 0 opacity 0.24 radius 1 x 0 y 1 body 内に変数として定義することもできる 使われているのを見たことがないのと、 View を let で保持するのはよくなさそう
  15. View の分割方法(ストアドプロパティ) monsterLabel Label { (name) } icon { (iconName)

    . () }. (.monsterListCell) body: View { monsterLabel . ( ) . ( (.systemBackground) . ( ) . ( : . ( : , : ), : , : , : ) ) } private let = : var some init Text Image resizable labelStyle padding 16 background Color cornerRadius 3 shadow color white 0 opacity 0.24 radius 1 x 0 y 1 他のプロパティを参照しているとエラーになる
 cannot use instance member 'name' within property initializer; property initializers run before 'self' is available Apple のサンプルコードでも使われていないように見える
  16. View の分割方法(コンピューテッドプロパティ) body: View { monsterLabel . ( ) .

    ( (.systemBackground) . ( ) . ( : . ( : , : ), : , : , : ) ) } monsterLabel: View { Label { (name) } icon { (iconName) . () } . (.monsterListCell) } var some init private var some : padding 16 background Color cornerRadius 3 shadow color white 0 opacity 0.24 radius 1 x 0 y 1 Text Image resizable labelStyle Apple のサンプルコードでよく使われている しかしメソッドのみ使うという人も多そう
  17. View の分割方法(メソッド) body: View { () . ( ) .

    ( (.systemBackground) . ( ) . ( : . ( : , : ), : , : , : ) ) } () View { Label { (name) } icon { (iconName) . () } . (.monsterListCell) } var some init private func -> some : monsterLabel padding 16 background Color cornerRadius 3 shadow color white 0 opacity 0.24 radius 1 x 0 y 1 Text Image resizable labelStyle monsterLabel View に引数を渡す場合は
 プロパティでなくメソッドを使う
  18. View の分割方法(構造体) body: View { ( : iconName, : name)

    . ( ) . ( (.systemBackground) . ( ) . ( : . ( : , : ), : , : , : ) ) } : View { iconName: name: body: View { Label { (name) } icon { (iconName) . () } . (.monsterListCell) } } var some init private struct var var var some : MonsterLabel iconName name padding 16 background Color cornerRadius 3 shadow color white 0 opacity 0.24 radius 1 x 0 y 1 String String Text Image resizable labelStyle MonsterLabel 親 View のプロパティを直接参照できないので、
 単純な View だと冗長かもしれない View の外に出せるので、複数の View で利用する場合に使う
  19. 変数 ストアドプロパティ コンピューテッド
 プロパティ メソッド 構造体 使わない 使わない 引数なし、かつ単純な View


    引数あり、かつ単純な View 複数の View で利用する、または複雑な View View の分割方法まとめ
  20. struct var var var some init private var some :

    : View { iconName: name: body: View { monsterLabel . ( ) . ( (.systemBackground) . ( ) . ( : . ( : , : ), : , : , : ) ) } monsterLabel: View { Label { (name) } icon { (iconName) . () } . (.monsterListCell) } } MonsterListCellView String String padding 16 background Color cornerRadius 3 shadow color white 0 opacity 0.24 radius 1 x 0 y 1 Text Image resizable labelStyle
  21. struct var var var some init private var some :

    : View { iconName: name: body: View { monsterLabel . ( ) . ( (.systemBackground) . ( ) . ( : . ( : , : ), : , : , : ) ) } monsterLabel: View { Label { (name) } icon { (iconName) . () } . (.monsterListCell) } } MonsterListCellView String String padding 16 background Color cornerRadius 3 shadow color white 0 opacity 0.24 radius 1 x 0 y 1 Text Image resizable labelStyle マテリアルな影を
 切り出したい View の extension? ViewModifier?
  22. struct var var var some private var some : :

    View { iconName: name: body: View { monsterLabel . ( ) . ( (.systemBackground) . ( ) . ( : ) ) } monsterLabel: View { Label { (name) } icon { (iconName) . () } . (.monsterListCell) } } MonsterListCellView String String padding 16 background Color cornerRadius 3 elevate elevation 1 Text Image resizable labelStyle extension func -> some init { ( : CGFloat) View { ( : . ( : , : ), : elevation, : , : elevation ) } } View elevate elevation shadow color white 0 opacity 0.24 radius x 0 y View+Material 単純なモディファイアなので
 ViewModifier を使わないことにする
  23. モディファイアの分割方法(extension) extension func -> some init { ( : CGFloat)

    View { ( : . ( : , : ), : elevation, : , : elevation ) } } View elevate elevation shadow color white 0 opacity 0.24 radius x 0 y モディファイアは View に生えているメソッドで、
 View を返す、とわかるといろいろ捗る
 イメージ View → モディファイアを適用 → 新しい View が返る なので extension でも基本的には同じことが実現できる
  24. モディファイアの分割方法(ViewModifier + extension) モディファイアは View に生えているメソッドで、
 View を返す、とわかるといろいろ捗る
 イメージ View

    → モディファイアを適用 → 新しい View が返る extension func -> some private struct var func -> some init { ( : CGFloat) View { ( ( : elevation)) } } : ViewModifier { elevation: CGFloat ( : Content) View { content. ( : . ( : , : ), : elevation, : , : elevation ) } } View elevate elevation Elevate body content modifier Elevate elevation shadow color white 0 opacity 0.24 radius x 0 y 単純なモディファイアの場合、 ViewModifier を噛ませると 冗長になる
  25. モディファイアの分割方法(ViewModifier + extension) モディファイアは View に生えているメソッドで、
 View を返す、とわかるといろいろ捗る
 イメージ View

    → モディファイアを適用 → 新しい View が返る extension func -> some private struct var func -> some init { ( : CGFloat) View { ( ( : elevation)) } } : ViewModifier { elevation: CGFloat ( : Content) View { content. ( : . ( : , : ), : elevation, : , : elevation ) } } View elevate elevation Elevate body content modifier Elevate elevation shadow color white 0 opacity 0.24 radius x 0 y 単純なモディファイアの場合、 ViewModifier を噛ませると 冗長になる ViewModifier は extension でラップすると モディファイアらしく使える ViewModifier 単体だと使いづらい
  26. extension のみ ViewModifier のみ extension + ViewModifier 基本的にはこちらを使う 使わない(必ず extension

    でラップする) extension のみで実現できない場合に使う モディファイアの分割方法まとめ
  27. struct var var var some private var some : :

    View { iconName: name: body: View { monsterLabel . ( ) . ( (.systemBackground) . ( ) . ( : ) ) } monsterLabel: View { Label { (name) } icon { (iconName) . () } . (.monsterListCell) } } MonsterListCellView String String padding 16 background Color cornerRadius 3 elevate elevation 1 Text Image resizable labelStyle
  28. struct var var var some private var some : :

    View { iconName: name: body: View { monsterLabel . ( ) . ( (.systemBackground) . ( ) . ( : ) ) } monsterLabel: View { Label { (name) } icon { (iconName) . () } . (.monsterListCell) } } MonsterListCellView String String padding 16 background Color cornerRadius 3 elevate elevation 1 Text Image resizable labelStyle View の分割場所はどうする? body? 同一 View? 同一ファイル? 別ファイル?
  29. View の分割場所(body) body: View { monsterLabel Label { (name) }

    icon { (iconName) . () }. (.monsterListCell) monsterLabel . ( ) . ( (.systemBackground) . ( ) . ( : . ( : , : ), : , : , : ) ) } var some let = : init Text Image resizable labelStyle padding 16 background Color cornerRadius 3 shadow color white 0 opacity 0.24 radius 1 x 0 y 1 body 内だと変数として定義するしかない let で保持するのは避けたい
  30. View の分割場所(同一 View) struct var var var some private var

    some : : View { iconName: name: body: View { monsterLabel . ( ) . ( (.systemBackground) . ( ) . ( : ) ) } monsterLabel: View { Label { (name) } icon { (iconName) . () } . (.monsterListCell) } } MonsterListCellView String String padding 16 background Color cornerRadius 3 elevate elevation 1 Text Image resizable labelStyle 他で使わないなら 同一 View でよさそう
  31. View の分割場所(同一ファイルの別 View) struct var var var some private struct

    var var var some : : View { iconName: name: body: View { ( : iconName, : name) . ( ) . ( (.systemBackground) . ( ) . ( : ) ) } } : View { iconName: name: body: View { Label { (name) } icon { (iconName) . () } . (.monsterListCell) } } MonsterListCellView MonsterLabel String String MonsterLabel iconName name padding 16 background Color cornerRadius 3 elevate elevation 1 String String Text Image resizable labelStyle 単一の View のみで使うなら、View の外に出す必要はない エクステンションは構造体内に定義できないので出すしかない
  32. View の分割場所(別ファイル) struct var var var some : : View

    { iconName: name: body: View { Label { (name) } icon { (iconName) . () } . (.monsterListCell) } } MonsterLabel String String Text Image resizable labelStyle 複数の View で利用するなら別ファイルがいい
  33. body 同一 View 同一ファイル
 の別 View 別ファイル 使わない プロパティ、メソッド、単一の View

    で利用する構造体 同一ファイル内の複数の View で利用する構造体、
 単一の View で利用するエクステンション 複数の View で利用する構造体・エクステンション View の分割場所まとめ
  34. struct var var var some private var some : :

    View { iconName: name: body: View { monsterLabel . ( ) . ( (.systemBackground) . ( ) . ( : ) ) } monsterLabel: View { Label { (name) } icon { (iconName) . () } . (.monsterListCell) } } MonsterListCellView String String padding 16 background Color cornerRadius 3 elevate elevation 1 Text Image resizable labelStyle
  35. struct var var var some private var some : :

    View { iconName: name: body: View { monsterLabel . ( ) . ( (.systemBackground) . ( ) . ( : ) ) } monsterLabel: View { Label { (name) } icon { (iconName) . () } . (.monsterListCell) } } MonsterListCellView String String padding 16 background Color cornerRadius 3 elevate elevation 1 Text Image resizable labelStyle .padding(_:) は 切り出したプロパティに含めないの?
  36. struct var var var some private var some : :

    View { iconName: name: body: View { monsterLabel . ( ) . ( (.systemBackground) . ( ) . ( : ) ) } monsterLabel: View { Label { (name) } icon { (iconName) . () } . (.monsterListCell) } } MonsterListCellView String String padding 16 background Color cornerRadius 3 elevate elevation 1 Text Image resizable labelStyle .padding(_:) は 切り出したプロパティに含めないの? →外から決めるべきことは含めない パディングは Label 単体では決まらず、 配置場所によって決まる
  37. struct func -> some extension where static var init :

    LabelStyle { ( : Configuration) View { ( : ) { configuration.icon . () . ( : , : ) configuration.title . (.title) . ( : . , : .leading) } } } Self == MonsterListCellLabelStyle { monsterListCell: MonsterListCellLabelStyle { . () } } MonsterListCellLabelStyle makeBody configuration LabelStyle HStack spacing 32 scaledToFit frame width 68 height 68 font frame maxWidth infinity alignment MonsterListCellLabelStyle
  38. struct func -> some extension where static var init :

    LabelStyle { ( : Configuration) View { ( : ) { configuration.icon . () . ( : , : ) configuration.title . (.title) . ( : . , : .leading) } } } Self == MonsterListCellLabelStyle { monsterListCell: MonsterListCellLabelStyle { . () } } MonsterListCellLabelStyle makeBody configuration LabelStyle HStack spacing 32 scaledToFit frame width 68 height 68 font frame maxWidth infinity alignment サイズを固定する場合は切り出す MonsterListCellLabelStyle
  39. struct func -> some extension where static var init :

    LabelStyle { ( : Configuration) View { ( : ) { configuration.icon . () . ( : , : ) configuration.title . (.title) . ( : . , : .leading) } } } Self == MonsterListCellLabelStyle { monsterListCell: MonsterListCellLabelStyle { . () } } MonsterListCellLabelStyle makeBody configuration LabelStyle HStack spacing 32 scaledToFit frame width 68 height 68 font frame maxWidth infinity alignment 左寄せにするのが目的で 外から決めるものではない MonsterListCellLabelStyle
  40. struct var var var some private var some : :

    View { iconName: name: body: View { monsterLabel . ( ) . ( (.systemBackground) . ( ) . ( : ) ) } monsterLabel: View { Label { (name) } icon { (iconName) . () } . (.monsterListCell) } } MonsterListCellView String String padding 16 background Color cornerRadius 3 elevate elevation 1 Text Image resizable labelStyle
  41. struct var var var some private var some : :

    View { iconName: name: body: View { monsterLabel . ( ) . ( (.systemBackground) . ( ) . ( : ) ) } monsterLabel: View { Label { (name) } icon { (iconName) . () } . (.monsterListCell) } } MonsterListCellView String String padding 16 background Color cornerRadius 3 elevate elevation 1 Text Image resizable labelStyle .elevateBackground(elevation:) のようにまとめたほうがよくない?
  42. struct var var var some private var some : :

    View { iconName: name: body: View { monsterLabel . ( ) . ( (.systemBackground) . ( ) . ( : ) ) } monsterLabel: View { Label { (name) } icon { (iconName) . () } . (.monsterListCell) } } MonsterListCellView String String padding 16 background Color cornerRadius 3 elevate elevation 1 Text Image resizable labelStyle .elevateBackground(elevation:) のようにまとめたほうがよくない? →そうかも どこまで分割するかは好みだけど、 body には「構造」と外から決めるものだけ残して 「装飾」は外出しすると見やすいかも
  43. 役割は1つ以上の View をラップする 単体 単体 単体 役割 単体 役割 単体

    単体 単体 役割 単体 役割 役割 View に役割を付与するイメージ
  44. 実画面と設計の対応イメージ 単体 単体 単体 役割 単体 役割 役割 ヘッダー 画面の一部分

    UINavigationBar UICollectionViewCell UICollectionViewCell UICollectionViewCell 画面全体
  45. 「View 単体の意味」の命名ルール プロトコルや型の名前を末尾に付ける struct var var var some private var

    some : : View { iconName: name: body: View { monsterLabel . ( ) . ( (.systemBackground) . ( ) . ( : ) ) } monsterLabel: View { Label { (name) } icon { (iconName) . () } MonsterListCellView String String padding 16 background Color cornerRadius 3 elevate elevation 1 Text Image resizable View プロトコルに準拠しているから「◦◦View」 型は View だが、 中身が Label のみなので「◦◦Label」
  46. 「View 単体の意味」の命名ルール Apple のサンプルコードや UIKit を参考にする(任意) struct var var var

    some private var some : : View { iconName: name: body: View { monsterLabel . ( ) . ( (.systemBackground) . ( ) . ( : ) ) } monsterLabel: View { Label { (name) } icon { (iconName) . () } MonsterListCellView String String padding 16 background Color cornerRadius 3 elevate elevation 1 Text Image resizable UICollectionViewCell のように使うので「◦◦CellView」