Swift and SwiftUI tutorials for Swift Developers

How to build a Rich Text Editor in SwiftUI

A Complete Step-by-Step Tutorial in Xcode

Introduction

Rich Text Editors are a core component in many modern applications: notes, email, messaging, blogging, productivity apps, and more. They allow users to apply styles such as bold, italic, underline, colors, font sizes, lists, and links, all while typing.

Historically, building a rich text editor on iOS was complex and required UIKit (UITextView) and NSAttributedString. With the evolution of SwiftUI, especially looking ahead to iOS 26, Apple has significantly improved rich text support, making it possible to build powerful editors without abandoning the declarative paradigm.

In this tutorial you will learn:

  • What a Rich Text Editor is and how it works internally
  • How to handle rich text using AttributedString
  • How to integrate an editable text view in SwiftUI
  • How to build a formatting toolbar (bold, italic, underline, color)
  • How to synchronize state, selection, and formatting
  • Best practices for architecture in SwiftUI

By the end, you’ll have a fully functional, extensible Rich Text Editor ready for production.


Prerequisites

Before starting, make sure you have:

  • Xcode compatible with iOS 26
  • Basic knowledge of Swift and SwiftUI
  • Familiarity with @State@Binding, and ObservableObject
  • A Mac running an up-to-date version of macOS

1. What Is a Rich Text Editor on iOS?

A Rich Text Editor is a component that allows editing text along with formatting information. On iOS, this formatting is represented by attributes attached to text ranges.

Examples of attributes include:

  • Font (font)
  • Color (foregroundColor)
  • Style (bold, italic)
  • Underline
  • Links
  • Alignment

In modern Swift, Apple encourages the use of:

AttributedString

instead of NSAttributedString, because it is:

  • Safer
  • More expressive
  • Fully compatible with SwiftUI

2. Editor Architecture

Our editor will have three main layers:

  1. Text model
    Uses AttributedString as the single source of truth.
  2. Editing view
    A SwiftUI component that renders and edits the text.
  3. Formatting toolbar
    Buttons that apply styles to the selected text.

Separating these layers keeps the code clean, scalable, and maintainable.


3. Creating the Project in Xcode

  1. Open Xcode
  2. Select Create a new Xcode project
  3. Choose App
  4. Configure:
    • Interface: SwiftUI
    • Language: Swift
    • Target: iOS 26

Name the project something like:

RichTextEditorSwiftUI

4. The Data Model: AttributedString

We start by creating an observable model that holds the text:

import SwiftUI

class RichTextViewModel: ObservableObject {
    @Published var text: AttributedString = AttributedString("Start typing your text here...")
}

This will be the central state of the editor.


5. Displaying Rich Text in SwiftUI

SwiftUI can render AttributedString directly:

Text(viewModel.text)

However, Text is not editable. To edit rich text, we need a dedicated editable component.


6. Using TextEditor with AttributedString

In iOS 26, TextEditor supports AttributedString directly:

struct RichTextEditorView: View {
    @StateObject private var viewModel = RichTextViewModel()

    var body: some View {
        TextEditor(text: $viewModel.text)
            .padding()
    }
}

With this, you already have a basic rich text editor.


7. Tracking Text Selection

To apply formatting, we need to know which part of the text is selected.

@Published var selectedRange: Range<AttributedString.Index>?

In iOS 26, TextEditor exposes selection changes via modifiers:

.onSelectionChange { range in
    viewModel.selectedRange = range
}

8. Applying Formatting: Bold

We create a function to toggle bold formatting on the selected text:

func toggleBold() {
    guard let range = selectedRange else { return }

    let isBold = text[range].font?.weight == .bold

    text[range].font = .system(
        size: 16,
        weight: isBold ? .regular : .bold
    )
}

This approach modifies only the selected range.


9. Formatting Toolbar

Next, we build a toolbar with formatting buttons:

struct FormatToolbar: View {
    @ObservedObject var viewModel: RichTextViewModel

    var body: some View {
        HStack {
            Button("B") {
                viewModel.toggleBold()
            }
            .font(.system(size: 18, weight: .bold))

            Button("I") {
                viewModel.toggleItalic()
            }
            .italic()

            Button("U") {
                viewModel.toggleUnderline()
            }
        }
        .padding()
        .background(.ultraThinMaterial)
    }
}

10. Italic and Underline

We implement the remaining formatting functions:

func toggleItalic() {
    guard let range = selectedRange else { return }
    text[range].font = .system(size: 16).italic()
}

func toggleUnderline() {
    guard let range = selectedRange else { return }
    text[range].underlineStyle =
        text[range].underlineStyle == nil ? .single : nil
}

11. Text Color

We add support for text color:

func setTextColor(_ color: Color) {
    guard let range = selectedRange else { return }
    text[range].foregroundColor = color
}

And color buttons:

Button {
    viewModel.setTextColor(.red)
} label: {
    Circle().fill(Color.red).frame(width: 20)
}

12. Putting Everything Together

struct ContentView: View {
    @StateObject private var viewModel = RichTextViewModel()

    var body: some View {
        VStack(spacing: 0) {
            FormatToolbar(viewModel: viewModel)

            TextEditor(text: $viewModel.text)
                .padding()
        }
    }
}

13. Saving and Loading Rich Text

You can serialize the text to Data:

let data = try? viewModel.text.data(using: .rtf)

And restore it later:

if let data {
    viewModel.text = try AttributedString(data: data)
}

This is ideal for Core Data, files, or iCloud storage.


14. Best Practices

✔ Use AttributedString as the single source of truth
✔ Avoid unnecessary UIKit bridging
✔ Centralize formatting logic in the ViewModel
✔ Keep the UI declarative
✔ Design with extensibility in mind (lists, headers, links)


15. Ideas to Extend the Editor

  • Numbered and bulleted lists
  • Headings (H1, H2, H3)
  • Link support
  • Custom Undo / Redo
  • Export to Markdown or HTML
  • External keyboard support

Conclusion

Building a Rich Text Editor in SwiftUI for iOS 26 is no longer a complex task reserved for UIKit. Thanks to AttributedString, improvements in TextEditor, and the declarative model of SwiftUI, we can now build powerful, elegant, and maintainable editors.

This tutorial provides a solid, professional foundation for integrating advanced text editing into modern iOS applications. From here, the only limits are your imagination—and a bit of UX design.

If you have any questions about this article, please contact me and I will be happy to help you 🙂. You can contact me on my X profile or on my Instagram profile.

Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Article

Liquid Glass on Button in SwiftUI

Next Article

How to create a button in SwiftUI

Related Posts