---
title: Content Modeling in Apple
description: Learn how to handle custom blocks, render rich text, and use story references to manage content across your Apple project.
url: https://www.storyblok.com/docs/guides/apple/content-modeling
---

# Content Modeling in Apple

Learn how to handle different content-type and nestable blocks, render rich text, and use story references to manage content globally.

## Setup

In the existing space, create the following blocks:

-   An `article` content type block with the following fields:
    -   `title`: Text
    -   `content`: Rich text
-   A `featured-articles` nestable block with the following field:
    -   `articles`: References

> [!NOTE]
> Learn more about fields in the [fields concept](/docs/concepts/fields).

Next, create an `Articles` folder, open it, and create a few stories that use the `article` content type.

Finally, add the `featured-articles` block to the `body` field of the **Home** story, and select the created articles to feature.

## Create and register blocks

Update the `BlockLibrary.swift` file to add cases for the new blocks.

Example/BlockLibrary.swift

```swift
import StoryblokClient

@BlockLibrary
enum Block: Decodable, Hashable {
    case feature(name: String)
    case grid(columns: [Block])
    case page(body: [Block])
    case teaser(headline: String)
    case article(Article)
    case featuredArticles(articles: [Story<Article>])
    case unknown

    enum CodingKeys: String, CodingKey {
        case featuredArticles = "featured-articles"
    }

    nonisolated struct Article: Decodable, Hashable {
        let title: String
        let content: RichText<Block>
    }
}
```

The fields of the `article` and `featured-articles` blocks are defined as follows:

-   `Article.content` is defined as [`RichText<Block>`](https://storyblok.github.io/storyblok-swift/documentation/storyblokclient/richtext) which can represent any rich text node (a document in this case).
-   `featuredArticles.articles` is defined as `[Story<Article>]` which will cause the [referenced](https://www.storyblok.com/docs/concepts/references) stories to be [resolved](https://www.storyblok.com/docs/concepts/references#resolve-relations-in-api-requests) and returned. To receive only the UUIDs of the linked stories change the type of the field to `[UUID]`.
-   The [special nested enumeration](https://developer.apple.com/documentation/foundation/encoding-and-decoding-custom-types#Choose-Properties-to-Encode-and-Decode-Using-Coding-Keys) `CodingKeys` is declared to map the block’s technical name, `featured-articles`, which is not a valid Swift identifier, to the `featuredArticles` case.
-   Instead of labeled associated values for the individual fields of the block, the added `article` case declares the nested `Article` struct as a single unnamed associated value. This allows the sharing of the `Article` type with the `featuredArticles` case.

> [!WARNING]
> The optional `resolveLevel` parameter of the [`story()`](https://storyblok.github.io/storyblok-swift/documentation/storyblokclient/storyblokclient) function controls how deeply nested story references are resolved and defaults to one level deep. The API also limits the number of stories that can be resolved in a single request. Learn more in the [Content Delivery API documentation](/docs/api/content-delivery/v2/stories/retrieve-a-single-story) and the [References developer concept](/docs/concepts/references).
> 
> References that cannot be resolved can be handled in two ways:
> 
> -   Make the field optional (for example: `Story<Article>?`). Unresolved references are then set to `nil`.
> -   Change the field type from [`Story<Content>`](https://storyblok.github.io/storyblok-swift/documentation/storyblokclient/story) to [`UUID`](https://developer.apple.com/documentation/foundation/uuid) and resolve the stories in [subsequent requests](https://www.storyblok.com/docs/concepts/references#fetch-references-manually).

Next, add views for these blocks to the `View` conformance in `BlockView.swift`.

Example/BlockView.swift

```swift
import SwiftUI
import StoryblokClient
import RichTextView
…
extension Block: View {
    var body: some View {
        switch self {
        case let .page(body):
            LazyVStack(alignment: .leading, spacing: 0) {
                ForEach(body, id: \.self) { $0 }
            }
        …
        case let .featuredArticles(articles):
            VStack(alignment: .leading, spacing: 8) {
                Text("Featured Articles")
                    .font(.title2)
                ForEach(articles, id: \.self) { article in
                    NavigationLink(article.content.title, value: article)
                }
            }
            .padding()
        case let .article(article):
            article // Article conforms to View (below)
        case .unknown: Text("Unknown block type")
        }
    }
}

extension Block.Article: View {
    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Text(title)
                .font(.largeTitle)
            content
        }
        .padding()
    }
}
```

Views for `featuredArticles` and `article` are added alongside the existing blocks:

-   The `featuredArticles` view renders links to the featured articles, each a [`NavigationLink`](https://developer.apple.com/documentation/swiftui/navigationlink) carrying the article `Story` instance for immediate display of the selected article.
-   The `article` case returns the `Article` struct itself, whose `View` conformance is added in the extension below which places its rich text `content` directly in the view hierarchy, where it is rendered by the [`RichTextView`](https://storyblok.github.io/storyblok-swift/documentation/richtextview) library.

> [!TIP]
> The [`RichTextView`](https://storyblok.github.io/storyblok-swift/documentation/richtextview) library provides default SwiftUI views for all rich text [node types](https://storyblok.github.io/storyblok-swift/documentation/storyblokclient/richtext). You can [override](https://storyblok.github.io/storyblok-swift/documentation/richtextview/richtextviewdelegate) the view for any rich text node by conforming to `RichTextViewDelegate`, as described in the [RichTextView User Guide](https://storyblok.github.io/storyblok-swift/documentation/richtextview/userguide).

Finally, update `ExampleApp.swift` to add a navigation destination for article stories, and the [`onStoryLink`](https://storyblok.github.io/storyblok-swift/documentation/richtextview/userguide#Handling-story-links) modifier to handle any story links present in the rich text content.

Example/ExampleApp.swift

```swift
import SwiftUI
import StoryblokClient
import RichTextView
internal import URLSessionExtension
…
                    .navigationDestination(for: Story<Block>.self) { story in
                        StoryView(client.story(story.uuid), story: story)
                    }
                    .navigationDestination(for: Story<Block.Article>.self) { story in
                        StoryView(client.story(story.uuid), story: story)
                    }
            }
            .onStoryLink { uuid, _ in path.append(uuid) }
        }
…
```

Run the app and see the home story with the links to the featured articles rendered. Tapping any link navigates to the article.

## Related resources

[Concept: Fields](/docs/concepts/fields)

[Concept: References](/docs/concepts/references)

[RichTextView SDK Reference](https://storyblok.github.io/storyblok-swift/documentation/richtextview)

## Pagination

-   [Previous: Dynamic Navigation in Apple](/docs/guides/apple/dynamic-routing)
-   [Next: Internationalization in Apple](/docs/guides/apple/internationalization)
