
はじめに
こんにちは!
株式会社ヤプリでiOSエンジニアをしている白数 (@cychow_app)です!
9/19 (金)〜9/21 (日)の3日間、iOSDC Japan 2025が開催されました。
また、9/20 (土) 13:55〜14:15 の枠でスポンサーセッションに登壇してきました!
本記事では、スポンサーセッション内でもお話しした「ノーコードアプリプラットフォームを支えるServer-Driven UI 〜Block UIアーキテクチャの設計と実装〜」に関する記事の後半部となります。
もし前半部をまだ読まれていない場合は以下のリンクより、ご参照いただければと思います!
3. "Block UI"の実装:宣言的UIとServer-Driven UIの両立
3.1 変更の伝播設計
前半部でも、Block UIはSwiftUIで構築していると述べていました。
ただ、UI構築画面(CMS)で設定したレイアウト情報などがどのようにしてBlock UIの6つの層に適用・反映しているのかについて、疑問をも持たれた方もおられるかと思います。
各層のデータの伝播の流れに焦点を当てて、見ていきたいと思います。

上記は実際のBlock UIにおけるデータの伝播の流れとなります。 Block UIの肝は、CMSの変更を安全かつ素早くアプリに伝播させる設計です。
- CMSでレイアウト/アピアランス/データを更新(UI構築画面の操作)
- Pageのデータ更新メソッドを発火させ、最新のLayout/Property/Dataを取得
- FundamentalViewを基に各層へレイアウト情報の変更を適用
- SwiftUIの宣言的UIにより自動的に全UIの再描画を実行
この一連の仕組みによって、高速かつ安全な更新が可能になります。
Pageの役割
実装上は、PageView(基底ビュー)内でPageデータ更新メソッドであるrefresh() メソッドをフックし、各APIから取得したデータを6層構造にマッピングしていきます。
ここでロード状態やエラー状態の管理を標準化しておくと、Block追加・差し替え時も安全に拡張できます。
public struct PageView: View { @StateObject private var pageProvider: PageProvider @StateObject private var operators: Operators @StateObject private var spaceStore = SpaceStore() @StateObject private var sizeStore = SizeStore() public init(url: URL, isPopup: Bool, operators: Operators) { ... } public var body: some View { if let page = pageProvider.page, let space = page.mainSpace { SpaceView(space: space, isPopup: isPopup) .onAppear { // Pageデータを更新 PageProvider.refresh() } ... } } }
3.2 FundamentalAppearance/FundamentalView:横断スタイルの標準化
FundamentalAppearanceは、margin / padding / borderWidth / borderColor / cornerRadius / background(色・画像URL)といった見た目の共通プロパティをひとまとめにした“外観定義”です。
struct FundamentalAppearance: FundamentalAppearancable { let margin: UIEdgeInsets var padding: UIEdgeInsets let borderWidth: UIEdgeInsets let borderColor: UIColor let backgroundColor: UIColor var backgroundImageURL: URL? let shadow: Shadow let radius: CGFloat ... }
実は、UI構築画面(CMS)の右ペインの設定項目がここに対応します。

そして、FundamentalViewは、各層のViewを含め任意のSwiftUIの View を横断的にラップし、このAppearanceを宣言的に適用するためのコンポーネントです。ジェネリクスを用いることで、Block/Item/Elementなど層が変わっても、同一の適用パイプラインを維持できます。
struct FundamentalView<Appearance: FundamentalAppearancable, Content: View>: View { let appearance: Appearance var imageInsets: UIEdgeInsets = .zero var backgroundImagePosition: VerticalPosition = .top var clearsImage: Axis.Set = [.horizontal, .vertical] @ViewBuilder let content: Content private var innerRadius: CGFloat { ... } var body: some View { EdgedView(edgeInsets: appearance.margin) { BorderedView(borderWidth: appearance.borderWidth, color: Color(appearance.borderColor), outerRadius: appearance.radius, innerRadius: innerRadius) { EdgedView(edgeInsets: appearance.padding) { content } .background( ... ) .mask(RoundedRectangle(cornerRadius: innerRadius, style: .circular)) } .shadow( ... ) } } }
4. まとめ
では、全体を「ノーコードアプリプラットフォームを支えるServer-Driven UI 〜Block UIアーキテクチャの設計と実装〜」のまとめとなります!
- Block UIはServer-Driven UIの特徴を活かしている。CMS編集が即時反映され、アップデート申請なしで運用速度を上げられる。
- 3つのAPI(Layout/Property/Data) でUIの「設計図」を合成し、アプリは SwiftUI(ネイティブ) で描画する。
- 6層構造(Page/Space/Group/Block/Item/Element) により、再利用性・拡張性・表現力を両立。
- FundamentalAppearance/FundamentalView で横断スタイルを標準化し、宣言的UIの再描画と組み合わせて、安全・高速した変更伝播を実現。
最後に
iOSDC Japanは2020年から毎年参加してきましたが、今年のiOSDCが初めての登壇となりました。
過去には技術勉強会などで5分程度のLighting Talkなどには登壇していましたが、20分間のトークは初めてで、
短くもなく、長くもなくでトーク内容を作成・整理にするのに非常に時間を要してしまいました💦
ただ、ヤプリには豊富な登壇経験を持っているメンバーが多数在籍していたこともあり、登壇資料を作成するのに何回も相談にのっていただき、私自身の中では過去最高に魅力的なセッションにすることができました✨. (iOSチームメンバーをはじめ、本当に感謝の気持ちで一杯です😭)
また、今回は前半と後半の2部構成で、Yappliの"Block UI"についてご紹介してきましたが、まだまだ本記事ではご紹介できていない内容もたくさんあります。
Block UIをはじめ、Yappliのサービスやシステムなどにご興味をお持ちの方はぜひカジュアル面談も行なっていますので、ぜひお話しできればと思います!