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

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

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. ウーニャ、しってる。

    みんな雰囲気で SwiftUI

    をつかってる。
    2022.09.10(Sat) 17:15 - 17:55

    iOSDC Japan 2022 day 0 Track A
    @the_uhooi

    View Slide

  2. iOS app developer @uhooi
    @uhooi
    @the_uhooi

    View Slide

  3. View Slide

  4. View Slide

  5. View Slide

  6. View Slide

  7. 今日は SwiftUI における View の

    「分割」と「命名」について

    みんなで議論したいです
    #iosdc #ウーニャ
    #a 感想をつぶやいてね!

    View Slide

  8. 好きな SwiftUI の View について

    ツイートしてみましょう!

    ※なければ「┌|▼▼|┘」とつぶやいてください
    #iosdc #a #ウーニャ

    View Slide

  9. ・分割と命名を工夫する主な目的は、可読性の向上

    ・View 層のみを対象とする

     ・MVC や MVVM でいう「V」

    ・本発表は私個人の意見

    ・(オンライン向け)スクショ OK!

     ・SNS やブログに使ってね
    注意

    View Slide

  10. 01 View の「分割」

    View Slide

  11. View Slide

  12. 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

    View Slide

  13. 現状: body 内が1つの大きな View になっている



    ゴール: body 内の View を分割して読みやすくする

    View Slide

  14. 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()

    View Slide

  15. Label

    View Slide

  16. View Slide

  17. Label を使うと画像とテキストに

    関連性を持たせられる!

    View Slide

  18. 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()

    View Slide

  19. 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()

    View Slide

  20. View Slide

  21. 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

    View Slide

  22. 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

    View Slide

  23. 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

    View Slide

  24. 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)

    View Slide

  25. 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)

    View Slide

  26. ・Label を使うと、画像とテキストに関連性を

     持たせられる

    ・Label のカスタマイズには LabelStyle を使う
    Label のまとめ

    View Slide

  27. View の分割方法

    View Slide

  28. 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

    View Slide

  29. 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 が

    少しボリューミー

    View Slide

  30. 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

    どう切り出すのがいい?


    プロパティ?

    メソッド?

    構造体?

    View Slide

  31. 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 だから

    プロパティに切り出すのが

    よさそう

    View Slide

  32. 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 で保持するのはよくなさそう

    View Slide

  33. 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 のサンプルコードでも使われていないように見える

    View Slide

  34. 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 のサンプルコードでよく使われている


    しかしメソッドのみ使うという人も多そう

    View Slide

  35. 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 に引数を渡す場合は

    プロパティでなくメソッドを使う

    View Slide

  36. 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 で利用する場合に使う

    View Slide

  37. 変数

    ストアドプロパティ

    コンピューテッド

    プロパティ

    メソッド

    構造体
    使わない

    使わない

    引数なし、かつ単純な View



    引数あり、かつ単純な View

    複数の View で利用する、または複雑な View
    View の分割方法まとめ

    View Slide

  38. モディファイアの分割方法

    View Slide

  39. 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 Slide

  40. 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?

    View Slide

  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
    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 を使わないことにする

    View Slide

  42. モディファイアの分割方法(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 でも基本的には同じことが実現できる

    View Slide

  43. モディファイアの分割方法(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 を噛ませると

    冗長になる

    View Slide

  44. モディファイアの分割方法(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 単体だと使いづらい

    View Slide

  45. View Slide

  46. extension のみ

    ViewModifier のみ

    extension + ViewModifier
    基本的にはこちらを使う

    使わない(必ず extension でラップする)

    extension のみで実現できない場合に使う
    モディファイアの分割方法まとめ

    View Slide

  47. View の分割場所

    View Slide

  48. 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 Slide

  49. 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?

    同一ファイル?

    別ファイル?

    View Slide

  50. 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 で保持するのは避けたい

    View Slide

  51. 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 でよさそう

    View Slide

  52. 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 の外に出す必要はない


    エクステンションは構造体内に定義できないので出すしかない

    View Slide

  53. View の分割場所(別ファイル)
    struct
    var
    var
    var some
    :
    : View {

    iconName:
    name:


    body: View {

    Label {

    (name)

    } icon {

    (iconName)

    . ()

    }

    . (.monsterListCell)

    }

    }
    MonsterLabel
    String

    String

    Text
    Image
    resizable
    labelStyle
    複数の View で利用するなら別ファイルがいい

    View Slide

  54. body

    同一 View

    同一ファイル

    の別 View

    別ファイル
    使わない

    プロパティ、メソッド、単一の View で利用する構造体

    同一ファイル内の複数の View で利用する構造体、

    単一の View で利用するエクステンション

    複数の View で利用する構造体・エクステンション
    View の分割場所まとめ

    View Slide

  55. モディファイアの分割是非

    View Slide

  56. 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 Slide

  57. 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(_:) は

    切り出したプロパティに含めないの?

    View Slide

  58. 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 単体では決まらず、

    配置場所によって決まる

    View Slide

  59. 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

    View Slide

  60. 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

    View Slide

  61. 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

    View Slide

  62. ・外から決めるべきなら切り出さない

     ・例: .padding(_:)、.frame(width:height:)

    ・余白やサイズを固定したいなら切り出す
    モディファイアの分割是非まとめ

    View Slide

  63. 分割の上限

    View Slide

  64. 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 Slide

  65. 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:)

    のようにまとめたほうがよくない?

    View Slide

  66. 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 には「構造」と外から決めるものだけ残して

    「装飾」は外出しすると見やすいかも

    View Slide

  67. ・どこまで分割するかは好み

    ・body には構造と外から決めるものだけ残すと

     読みやすい
    分割の上限まとめ

    View Slide

  68. View の「分割」まとめ

    View Slide

  69. ・Label など標準で用意されている View を活用する

    ・View を let で保持しない

    ・View の分割はスコープを最小にする

    ・body は「構造」のわかりやすさを意識する
    View の「分割」まとめ

    View Slide

  70. 02 View の「命名」

    View Slide

  71. 「View 単体の意味」と

    「各画面における役割」で分けて

    考えるべき

    View Slide

  72. View 単体の意味
    Label
    Image Text

    View Slide

  73. 各画面における役割
    画面全体(Page)
    画面の一部分

    (Organism)

    View Slide

  74. 役割は1つ以上の View をラップする
    単体
    単体
    単体
    役割
    単体
    役割
    単体
    単体
    単体
    役割
    単体
    役割
    役割
    View に役割を付与するイメージ

    View Slide

  75. 実画面と設計の対応イメージ
    単体
    単体
    単体
    役割
    単体
    役割
    役割
    ヘッダー
    画面の一部分
    UINavigationBar
    UICollectionViewCell
    UICollectionViewCell
    UICollectionViewCell
    画面全体

    View Slide

  76. 「View 単体の意味」の命名ルール

    View Slide

  77. 「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」

    View Slide

  78. 「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」

    View Slide

  79. ・プロトコルや型の名前を末尾に付ける

    ・Apple のサンプルコードや UIKit などを

     参考にする(任意)
    「View 単体の意味」命名まとめ

    View Slide

  80. 「各画面における役割」の命名ルール

    View Slide

  81. 「各画面における役割」の命名ルール
    ルールを決めたら README に明記する
    View プロトコルに準拠しているから「○○View」

    View Slide

  82. 「各画面における役割」の命名ルール
    アトミックデザインなどを参考にする(任意)
    View プロトコルに準拠しているから「○○View」

    View Slide

  83. 「各画面における役割」の命名ルール
    役割を細かく分け過ぎない
    View プロトコルに準拠しているから「○○View」
    SwiftUI で

    ここまで分けると

    冗長かもしれない

    View Slide

  84. ・ルールを決めたら README に明記する

    ・アトミックデザインなどを参考にする(任意)

    ・役割を細かく分け過ぎない
    「各画面における役割」命名まとめ

    View Slide

  85. View の「命名」まとめ

    View Slide

  86. ・View 単体の意味と、全体の役割を分けて考える

    ・全体の設計をドキュメント化する
    View の「命名」まとめ

    View Slide

  87. まとめ

    View Slide

  88. ・言語化するのが大事

    ・プロジェクトごとにルールをドキュメント化する

    ・できるところから適用してみてください

    ・今日の発表でわからなかったら

     何でも聞いてください!
    まとめ

    View Slide