---
title: SwiftUI Viewの肥大化対策 個人的5選
tags: 
author: [Takahiro Suzuki](https://image.docswell.com/user/sussan0416)
site: [Docswell](https://www.docswell.com/)
thumbnail: https://bcdn.docswell.com/page/47QVRX3NEP.jpg?width=480
description: SAPPORO ENGINEER BASE #10 モバイルアプリ開発 https://seb-sapporo.connpass.com/event/370615/ における、発表資料です。
published: October 31, 25
canonical: https://image.docswell.com/s/sussan0416/KEYDN8-swiftui-view-5
---
# Page. 1

![Page Image](https://bcdn.docswell.com/page/47QVRX3NEP.jpg)

SwiftUI Viewの肥大化対策
個人的5選
鈴木孝宏 (sussan0416), 2025-10-31, SAPPORO ENGINEER BASE #10 モバイルアプリ開発


# Page. 2

![Page Image](https://bcdn.docswell.com/page/KE4M861371.jpg)

鈴木 孝宏
sussan0416 / sussan-po.com
• 株式会社Helpfeelのエンジニア
• 世界で2,200万人以上が使う画像共有サービス「Gyazo」のiOS・macOS
アプリ開発を担当
• 公立はこだて未来大学大学院在学中にIPA「未踏IT人材発掘・育成事業」に
採択
• SNSアプリや広告配信システム開発を経て、フィンテック、教育、研究、
ウェディングなど多様な分野のアプリに携わる
• 2025年からは札幌を拠点に、IoTや生成AIにも取り組みながらリモート開
発を継続
• 信条は「シンプルで使いやすいものを作ること」
※ ChatGPTに調査してもらい生成
※ さらに、Apple Intelligenceで要約


# Page. 3

![Page Image](https://bcdn.docswell.com/page/L7182VGZJG.jpg)

自分用のアプリですが、公開もしています
Suitoucho
シアトマワ


# Page. 4

![Page Image](https://bcdn.docswell.com/page/G7WZRLK672.jpg)

SwiftUIとは


# Page. 5

![Page Image](https://bcdn.docswell.com/page/4JZ1RMZRE3.jpg)

SwiftUIとは
シンプルで読みやすく、書きやすいUIフレームワーク
• Appleが提供するUIフレームワーク
struct CounterView: View {
@State private var count = 0
• 1つのコードで複数のAppleプラット
var body: some View {
VStack {
フォームに対応
Text(&quot;Count: \(count)&quot;)
Button(&quot;＋1&quot;) { count += 1 }
• 宣言型シンタックスを使用
}
}
}
Reactのようなもの、Jetpack Composeのようなもの、という理解で問題ありません。
詳しくは https://developer.apple.com/jp/swiftui/ を読んでみてください。


# Page. 6

![Page Image](https://bcdn.docswell.com/page/YE6LRNZ17V.jpg)

書きやすさにひそむ罠
struct CounterView: View {
// 状態
@State private var count = 0
// ロジック
func countUp() {
• 状態・見た目・ロジックを、
count += 1
}
1クラスの中に混在させがち
// 見た目
• 小さいViewだからと、
1ファイルでまとめてしまいがち
var body: some View {
VStack {
Text(&quot;Count: \(count)&quot;)
.foregroundStyle(
• その後、機能追加により、
count &gt;= 10 ? .red : .black
一気に構造がカオスになっていく
)
Button(&quot;＋1&quot;, action: countUp)
}
}
}


# Page. 7

![Page Image](https://bcdn.docswell.com/page/GE5185WLJ4.jpg)

SwiftUI特有の読みにくさ
Modi erが遠い
var body: some View {
List(album.songs) { song in
HStack {
Image(album.cover)
• Modi er: Viewの見た目や振る舞いを
.resizable()
.scaledToFill()
変更するためのメソッド
.clipped()
.frame(width: 40, height: 40)
• 必要な変更だけを書けるよさがある
.border(.gray, width: 1)
VStack(alignment: .leading) {
• 宣言と変更に距離が空くため、
Text(song.title)
Text(song.artist.name)
レビューのしにくさ、保守のしにくさ
を感じることがある
.foregroundStyle(.secondary)
}
}
}
̀List̀のModi er
.listStyle(.insetGrouped)
.refreshable(action: reload)
.onAppear(perform: loadAlbums)
fi
fi
fi
}


# Page. 8

![Page Image](https://bcdn.docswell.com/page/9721KNQ3ER.jpg)

課題感
• 宣言的シンタックスで、読みやすく書きやすい反面、
状態・ロジック・見た目が1クラスに混在しがち
• UIの見た目や振る舞いをメソッドチェーンで変更するため、
宣言と変更に距離ができる
今日の内容: これらを解消するために試してきたこと


# Page. 9

![Page Image](https://bcdn.docswell.com/page/DJYZNPW8EM.jpg)

肥大化対策でやっていること
個人的5選


# Page. 10

![Page Image](https://bcdn.docswell.com/page/V7N3X59978.jpg)

目次
• ViewBuilder: 共通のUI宣言を切り出す
• Modi er: 包括的なUIを切り出す
• Generics: UIの詳細をあとで決める
• Environment: 値や処理を外から注入する
fi
• Delegate: 処理を委譲する


# Page. 11

![Page Image](https://bcdn.docswell.com/page/YJ998Y23J3.jpg)

ViewBuilder: 共通のUI宣言を切り出す
メソッドに出すだけでも良い
Before
BookListItem(
name: book.name, balance: book.balance
)
.contextMenu {
Button(role: .destructive) {
// action
} label: {
Label(&quot;Delete Book&quot;, systemImage: &quot;trash&quot;)
}
Button {
// action
} label: {
Label(&quot;Edit book name&quot;, systemImage: &quot;square.and.pencil&quot;)
}
}
.swipeActions(allowsFullSwipe: false) {
// 同じメニューを表示
}
contextMenuとswipeActionsの内容が共通


# Page. 12

![Page Image](https://bcdn.docswell.com/page/GJ89865LED.jpg)

ViewBuilder: 共通のUI宣言を切り出す
メソッドに出すだけでも良い
Before
BookListItem(
name: book.name, balance: book.balance
)
切り出しちゃって良いはず
.contextMenu {
Button(role: .destructive) {
// action
} label: {
Label(&quot;Delete Book&quot;, systemImage: &quot;trash&quot;)
@ViewBuilder menuItems: () -&gt; MenuItem,
}
MenuItem: View
Button {
// action
} label: {
Label(&quot;Edit book name&quot;, systemImage: &quot;square.and.pencil&quot;)
}
}
.swipeActions(allowsFullSwipe: false) {
// 同じメニューを表示
@ViewBuilder content: () -&gt; T, T: View
}


# Page. 13

![Page Image](https://bcdn.docswell.com/page/LJLWR9YQ7R.jpg)

ViewBuilder: 共通のUI宣言を切り出す
メソッドに出すだけでも良い
After
BookListItem(
name: book.name, balance: book.balance
)
.contextMenu {
buildItemMenus(book)
}
.swipeActions(allowsFullSwipe: false) {
buildItemMenus(book)
}
↑modi erの距離が近くなり、
fi
仕様の見通しが良くなった👍
@ViewBuilder
private func buildItemMenus(_ book: Book) -&gt; some View {
Button(role: .destructive) {
// action
} label: {
Label(&quot;Delete Book&quot;, systemImage: &quot;trash&quot;)
}
Button {
// action
} label: {
Label(
&quot;Edit book name&quot;,
systemImage: &quot;square.and.pencil&quot;
)
}
}


# Page. 14

![Page Image](https://bcdn.docswell.com/page/47M9RZDKJW.jpg)

Modi er: 包括的なUIを切り出す
例: 強制ログアウトのエラー表示
/// 通信エラー。あればアラート表示
Before
@State private var error: Error?
@State private var isShowError: Bool = false
var body: some View {
List(animals) { animal in
Text(animal.name)
}
.task {
do {
try await fetchAnimals()
} catch {
self.error = error
isShowError = true
}
}
.alert(
&quot;Error&quot;,
isPresented: $isShowError,
presenting: error
) { e in
Button(&quot;ログアウトする&quot;) { /**/ }
} message: { e in
Text(e.localizedDescription)
}
fi
}
← 定義が遠くなりがち
← errorの代入
← 使い回しがちな̀alert̀


# Page. 15

![Page Image](https://bcdn.docswell.com/page/P7RGR646J9.jpg)

Modi er: 包括的なUIを切り出す
例: 強制ログアウトのエラー表示
After
struct LogoutAlertModifier: ViewModifier {
/// 通信エラー。あればアラート表示
@State private var error: Error?
@State private var isShowError: Bool = false
func body(content: Content) -&gt; some View {
content
.alert(
&quot;Error&quot;,
isPresented: $isShowError,
presenting: error
) { e in
Button(&quot;ログアウトする&quot;) { /**/ }
} message: { e in
Text(e.localizedDescription)
}
.onReceive(.logoutError) { e in
error = e as? Error
isShowError = true
}
var body: some View {
List(animals) { animal in
Text(animal.name)
}
.task {
do {
try await fetchAnimals()
} catch {
notifyError(name: .logoutError, object: error)
}
}
.modifier(LogoutAlertModifier())
}
↑エラーは、Noti cationで投げるだけ👍
← Noti cationを受けるだけ👍
}
fi
※ Noti cation以外にも、エラーを伝搬させる方法は複数ある
※ Viewを操作する時は、MainActorで処理する点に注意（省略した）
fi
fi
fi
}


# Page. 16

![Page Image](https://bcdn.docswell.com/page/PJXXRM2DJX.jpg)

Modi er: 包括的なUIを切り出す
例: アプリ全体の共通処理
アプリがアクティブになった時の処理
Modi erを、最上位でセットするだけ
struct AppActiveProcessModifier: ViewModifier {
@Environment(\.scenePhase) var phase
var body: some Scene {
WindowGroup {
ContentView()
.modifier(AppActiveProcessModifier())
}
}
func body(content: Content) -&gt; some View {
content
.onChange(of: phase) { _, newPhase in
if newPhase == .active {
onAppActive()
}
}
}
アプリ全体の共通処理などにも応用可能👍
責務がはっきりして良い感じ。
fi
fi
※ 参照: https://sussan-po.com/2023/03/24/swiftui-view-style/


# Page. 17

![Page Image](https://bcdn.docswell.com/page/3JKWRGMDJD.jpg)

Generics: UIの詳細をあとで決める
Before
struct AnimalsView: View {
var body: some View {
NavigationStack {
ScrollView {
TopAnimalView()
LazyVGrid(columns: columnsConfig) {
ForEach(animalItems) { animal in
NavigationLink {
AnimalDetailView(animal: animal)
} label: {
ThumbnailImage(image: animal.image)
}
}
}
}
}
}
↑3つの意図が混ざっている


# Page. 18

![Page Image](https://bcdn.docswell.com/page/LE318NYP75.jpg)

Generics: UIの詳細をあとで決める
ナビゲーション
struct AnimalsView: View {
var body: some View {
NavigationStack {
ScrollView {
TopAnimalView()
LazyVGrid(columns: columnsConfig) {
ForEach(animalItems) { animal in
NavigationLink {
AnimalDetailView(animal: animal)
} label: {
ThumbnailImage(image: animal.image)
}
}
}
}
}
}


# Page. 19

![Page Image](https://bcdn.docswell.com/page/8EDXMW53JG.jpg)

Generics: UIの詳細をあとで決める
レイアウト
struct AnimalsView: View {
var body: some View {
NavigationStack {
ScrollView {
TopAnimalView()
LazyVGrid(columns: columnsConfig) {
ForEach(animalItems) { animal in
NavigationLink {
AnimalDetailView(animal: animal)
} label: {
ThumbnailImage(image: animal.image)
}
}
}
}
}
}


# Page. 20

![Page Image](https://bcdn.docswell.com/page/V7PPR9GP78.jpg)

Generics: UIの詳細をあとで決める
詳細
struct AnimalsView: View {
var body: some View {
NavigationStack {
ScrollView {
TopAnimalView()
LazyVGrid(columns: columnsConfig) {
ForEach(animalItems) { animal in
NavigationLink {
AnimalDetailView(animal: animal)
} label: {
ThumbnailImage(image: animal.image)
}
}
}
}
}
}


# Page. 21

![Page Image](https://bcdn.docswell.com/page/2JV2RM6VJQ.jpg)

Generics: UIの詳細をあとで決める
struct AnimalsView: View {
var body: some View {
NavigationStack {
ScrollView {
TopAnimalView()
LazyVGrid(columns: columnsConfig) {
ForEach(animalItems) { animal in
NavigationLink {
AnimalDetailView(animal: animal)
} label: {
ThumbnailImage(image: animal.image)
}
}
}
}
}
}
Viewのナビゲーションと、レイアウトと、詳細が混在


# Page. 22

![Page Image](https://bcdn.docswell.com/page/5EGRMGN1JL.jpg)

Generics: UIの詳細をあとで決める
レイアウトを切り出す
After
struct AnimalsView: View {
var body: some View {
NavigationStack {
HeaderAndGridScrollView(items: animalItems) {
TopAnimalView()
} content: { animal in
NavigationLink {
AnimalDetailView(animal: animal)
} label: {
ThumbnailImage(image: animal.image)
}
}
}
}
struct HeaderAndGridScrollView&lt;T, Header, Content&gt;: View
where
T: RandomAccessCollection, T.Element: Identifiable,
Header: View, Content: View
{
let items: T
@ViewBuilder var header: () -&gt; Header
@ViewBuilder var content: (T.Element) -&gt; Content
var body: some View {
ScrollView {
header()
LazyVGrid(columns: columnsConfig) {
ForEach(items) { item in
content(item)
}
}
}
}


# Page. 23

![Page Image](https://bcdn.docswell.com/page/4JQVRXQN7P.jpg)

Generics: UIの詳細をあとで決める
レイアウトを切り出す
After
struct AnimalsView: View {
var body: some View {
NavigationStack {
HeaderAndGridScrollView(items: animalItems) {
TopAnimalView()
} content: { animal in
NavigationLink {
AnimalDetailView(animal: animal)
} label: {
ThumbnailImage(image: animal.image)
}
}
}
}
struct HeaderAndGridScrollView&lt;T, Header, Content&gt;: View
where
T: RandomAccessCollection, T.Element: Identifiable,
Header: View, Content: View
{
let items: T
@ViewBuilder var header: () -&gt; Header
@ViewBuilder var content: (T.Element) -&gt; Content
var body: some View {
ScrollView {
header()
LazyVGrid(columns: columnsConfig) {
ForEach(items) { item in
content(item)
}
}
↑Viewの意図が明確になり、見通しが良くなった👍
}
}
↑レイアウトを再利用できる👍


# Page. 24

![Page Image](https://bcdn.docswell.com/page/K74M86N3J1.jpg)

Environment: 値や処理を外から注入する
• Environment: Viewの子孫へデータ
var body: some View {
HeaderAndGridScrollView(items: animalItems) {
を伝搬させる仕組み
TopAnimalView()
} content: { animal in
NavigationLink {
• 子孫のViewは、̀@Environment̀
AnimalDetailView(animal: animal)
を使ってデータを取り出す
} label: {
ThumbnailImage(image: animal.image)
}
• ReactのContextに似ている
}
.environment(\.userType, user.type)
}
// 子孫Viewへ伝搬


# Page. 25

![Page Image](https://bcdn.docswell.com/page/LJ182V5ZEG.jpg)

Environment: 値や処理を外から注入する
例: Analytics
Before
struct CTAButton: View {
var body: some View {
Button(&quot;Subscribe now!&quot;) {
startSubscription()
Analytics.logEvent(&quot;subscription_start&quot;)
}
}
...
}
← 各所でimportしがちな FirebaseAnalytics
何度も出現するから切り出したい


# Page. 26

![Page Image](https://bcdn.docswell.com/page/GJWZRL56J2.jpg)

Environment: 値や処理を外から注入する
例: Analytics
protocol AnalyticsService {
func logEvent(name: String, parameters: [String: Any]?)
}
struct AnalyticsServiceImpl: AnalyticsService {
func logEvent(name: String, parameters: [String: Any]?) {
Analytics.logEvent(name, parameters: parameters)
}
}
← 処理を切り出す


# Page. 27

![Page Image](https://bcdn.docswell.com/page/4EZ1RMNR73.jpg)

Environment: 値や処理を外から注入する
例: Analytics
protocol AnalyticsService {
func logEvent(name: String, parameters: [String: Any]?)
}
struct AnalyticsServiceImpl: AnalyticsService {
func logEvent(name: String, parameters: [String: Any]?) {
Analytics.logEvent(name, parameters: parameters)
}
}
extension EnvironmentValues {
@Entry var useAnalytics: AnalyticsService? = nil
}
← EnvironmentValuesで取れるようにする
ここで初期値をセットしても良い
値が変わらないなら̀@Entrỳである必要もない


# Page. 28

![Page Image](https://bcdn.docswell.com/page/Y76LRN81JV.jpg)

Environment: 値や処理を外から注入する
例: Analytics
After
struct CTAButton: View {
@Environment(\.useAnalytics) private var analytics
← @Environmentで取り出す
var body: some View {
Button(&quot;Subscribe now!&quot;) {
startSubscription()
analytics?.logEvent(name: &quot;subscription_start&quot;, parameters: nil)
← 使用する
}
}
...
}
Environmentだから、Analyticsをモックに差し替えできる👍
※ この例では、上位のViewでインスタンスをセットする必要がある
※ パフォーマンスについては、未調査


# Page. 29

![Page Image](https://bcdn.docswell.com/page/G751859LE4.jpg)

Environment: 値や処理を外から注入する
例: ログインユーザーの伝搬・変更
• ViewModelからユーザーを取り出すこともよくある
• ViewModel経由で取り出す場合の問題
• ユーザーを参照するだけなのにViewModelが必要だったり
̀viewModel.user̀
• Previewするにも、ViewModelのモックが必要になったり
• 変更用のメソッドとかもモック……
本当は、値だけ、セッターだけを使いたい


# Page. 30

![Page Image](https://bcdn.docswell.com/page/9J21KN237R.jpg)

Environment: 値や処理を外から注入する
例: ログインユーザーの伝搬・変更
extension EnvironmentValues {
@Entry var currentUser: User? = nil
@Entry var setCurrentUser: SetCurrentUser = .init(user: .constant(nil))
}
struct SetCurrentUser {
private let user: Binding&lt;User?&gt;
init(user: Binding&lt;User?&gt;) {
self.user = user
}
func callAsFunction(_ user: User) {
self.user.wrappedValue = user
}
←値だけ、セッターだけを取れるようにする
←変更処理は、structに閉じる
←structを関数のように呼べる仕組み
}
fi
fi
fi
struct CurrentUserModifier: ViewModifier {
@State private var currentUser: User? = nil
func body(content: Content) -&gt; some View {
content
.environment(\.currentUser, currentUser)
.environment(\.setCurrentUser, SetCurrentUser(user: $currentUser))
}
}
←状態管理は、Modi erに閉じる
Modi er便利👍
アプリの最上位でModi erを適用する


# Page. 31

![Page Image](https://bcdn.docswell.com/page/DEYZNPY87M.jpg)

Environment: 値や処理を外から注入する
例: ログインユーザーの伝搬・変更
struct LoginView: View {
@Environment(\.currentUser) private var currentUser: User?
← 値だけを取り出せる👍
var body: some View {
if let currentUser {
Text(currentUser.name)
}
LoginButtonView()
}
}
struct LoginButtonView: View {
@Environment(\.setCurrentUser) var setCurrentUser
← セッターだけを使えるようになる👍
var body: some View {
Button(&quot;Login&quot;) {
Task {
try? await Task.sleep(for: .seconds(1))
setCurrentUser(.init(name: &quot;John Doe&quot;))
← structを関数のように呼べる
}
}
}
}
※ 参考: @Environment(\.keyPath)実践入門, https://qiita.com/lovee/items/0893d3ed7813e66d8188
※ 昔はクロージャをEnvironmentに入れていたけど、2024年のiOSDCでこれ↑を読んで、Callable Structを使うように変えた


# Page. 32

![Page Image](https://bcdn.docswell.com/page/VJN3X5P9J8.jpg)

Delegate: 処理を委譲する
Before
struct SongView: View {
← アルバム詳細画面にある、曲リストのView
let viewModel: AlbumDetailViewModel
let song: Song
var body: some View {
HStack {
...
Button {
viewModel.setLike(song, !song.isLike)
← お気に入り機能がある
} label: {
Image(systemName: song.isLike ? &quot;heart.fill&quot; : &quot;heart&quot;)
}
}
}
}


# Page. 33

![Page Image](https://bcdn.docswell.com/page/YE998Y33E3.jpg)

Delegate: 処理を委譲する
Before
struct SongView: View {
let viewModel: AlbumDetailViewModel
let song: Song
var body: some View {
HStack {
...
Button {
viewModel.setLike(song, !song.isLike)
} label: {
Image(systemName: song.isLike ? &quot;heart.fill&quot; : &quot;heart&quot;)
}
}
}
}
仕様追加の例: プレイリスト画面を新設。同じViewを使いたい。など


# Page. 34

![Page Image](https://bcdn.docswell.com/page/GE8986ML7D.jpg)

Delegate: 処理を委譲する
uses
AlbumDetailViewModel
AlbumDetailView
creates
references
SongView
SongViewがViewModelに直接依存しているから、再利用しづらい


# Page. 35

![Page Image](https://bcdn.docswell.com/page/LELWR93QJR.jpg)

Delegate: 処理を委譲する
シンプルな例
After
親側
struct SongView: View {
let song: Song
let likeAction: () -&gt; Void
List(viewModel.album.songs) { song in
SongView(song: song) {
viewModel.setLike(song, !song.isLike)
}
}
← コールバック
var body: some View {
HStack {
...
Button {
likeAction() ← 親にコールバックする
} label: {
Image(systemName: song.isLike ? &quot;heart.fill&quot; : &quot;heart&quot;)
}
}
}
}
ViewModelに直接依存しなくて👍


# Page. 36

![Page Image](https://bcdn.docswell.com/page/4JM9RZMKEW.jpg)

Delegate: 処理を委譲する
シンプルな例
uses
AlbumDetailViewModel
AlbumDetailView
creates
action
SongView
ViewModelに直接依存しなくなった👍


# Page. 37

![Page Image](https://bcdn.docswell.com/page/PJRGR6V6E9.jpg)

Delegate: 処理を委譲する
シンプルな例
親側
struct SongView: View {
let song: Song
let likeAction: () -&gt; Void
List(viewModel.album.songs) { song in
SongView(song: song) {
viewModel.setLike(song, !song.isLike)
}
}
var body: some View {
HStack {
...
Button {
likeAction()
} label: {
Image(systemName: song.isLike ? &quot;heart.fill&quot; : &quot;heart&quot;)
}
}
}
}
コールバックが増えることになったらどうしよう……？


# Page. 38

![Page Image](https://bcdn.docswell.com/page/PEXXRMZDEX.jpg)

Delegate: 処理を委譲する
結局これに戻る?
owns
AlbumDetailViewModel
AlbumDetailView
conforms
creates
SongView
delegates
Delegate
これなら、コールバックが増えても引数が増えなくて良さそう👍


# Page. 39

![Page Image](https://bcdn.docswell.com/page/3EKWRG8DED.jpg)

Delegate: 処理を委譲する
結局これに戻る？
After
親側
protocol SongViewDelegate: AnyObject {
func didTapLike(for song: Song)
← funcが増えても安心👍
}
List(viewModel.album.songs) { song in
SongView(song: song, delegate: viewModel)
}
struct SongView: View {
let song: Song
let delegate: SongViewDelegate
extension AlbumDetailViewModel: SongViewDelegate {
func didTapLike(for song: Song) {
// like処理
← Delegateにした
↑ 引数渡しでもEnv.でも
var body: some View {
}
HStack {
...
Button {
delegate.didTapLike(for: song) ← Delegate経由で呼ぶ
} label: {
Image(systemName: song.isLike ? &quot;heart.fill&quot; : &quot;heart&quot;)
}
}
}
}
}


# Page. 40

![Page Image](https://bcdn.docswell.com/page/L7318N2PJ5.jpg)

Delegate: 処理を委譲する
結局これに戻る？
After
親側
protocol SongViewDelegate: AnyObject {
func didTapLike(for song: Song)
}
List(viewModel.album.songs) { song in
SongView(song: song, delegate: viewModel)
}
struct SongView: View {
let song: Song
let delegate: SongViewDelegate
extension AlbumDetailViewModel: SongViewDelegate {
func didTapLike(for song: Song) {
// like処理
var body: some View {
}
HStack {
...
Button {
delegate.didTapLike(for: song)
} label: {
Image(systemName: song.isLike ? &quot;heart.fill&quot; : &quot;heart&quot;)
}
}
}
}
}
View側でインタフェースを定義できる良さがある👍
※ パフォーマンスについては、未調査
※ このパターンは、まだ試してみているところ


# Page. 41

![Page Image](https://bcdn.docswell.com/page/87DXMWZ3EG.jpg)

まとめ


# Page. 42

![Page Image](https://bcdn.docswell.com/page/VJPPR9DPJ8.jpg)

まとめ
肥大化対策でやっていること個人的5選
• SwiftUIは書きやすい反面、Viewがすぐに肥大化しやすい
• 肥大化を解消すべく、これまで試してきた5つの工夫を紹介
• ViewBuilder / Modi er / Generics / Environment / Delegate
• 責務を小さく、再利用しやすいViewを実装することを、意識していきたい
fi
ベストプラクティスは未だわからず
設計の大海原を、引き続きさまよっています……


