View in English

  • Apple Developer
    • Get Started

    Explore Get Started

    • Overview
    • Learn
    • Apple Developer Program

    Stay Updated

    • Latest News
    • Hello Developer
    • Platforms

    Explore Platforms

    • Apple Platforms
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    • App Store

    Featured

    • Design
    • Distribution
    • Games
    • Accessories
    • Web
    • Home
    • CarPlay
    • Technologies

    Explore Technologies

    • Overview
    • Xcode
    • Swift
    • SwiftUI

    Featured

    • Accessibility
    • App Intents
    • Apple Intelligence
    • Games
    • Machine Learning & AI
    • Security
    • Xcode Cloud
    • Community

    Explore Community

    • Overview
    • Meet with Apple events
    • Community-driven events
    • Developer Forums
    • Open Source

    Featured

    • WWDC
    • Swift Student Challenge
    • Developer Stories
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Centers
    • Documentation

    Explore Documentation

    • Documentation Library
    • Technology Overviews
    • Sample Code
    • Human Interface Guidelines
    • Videos

    Release Notes

    • Featured Updates
    • iOS
    • iPadOS
    • macOS
    • watchOS
    • visionOS
    • tvOS
    • Xcode
    • Downloads

    Explore Downloads

    • All Downloads
    • Operating Systems
    • Applications
    • Design Resources

    Featured

    • Xcode
    • TestFlight
    • Fonts
    • SF Symbols
    • Icon Composer
    • Support

    Explore Support

    • Overview
    • Help Guides
    • Developer Forums
    • Feedback Assistant
    • Contact Us

    Featured

    • Account Help
    • App Review Guidelines
    • App Store Connect Help
    • Upcoming Requirements
    • Agreements and Guidelines
    • System Status
  • Quick Links

    • Events
    • News
    • Forums
    • Sample Code
    • Videos
 

Videos

Abrir menú Cerrar menú
  • Colecciones
  • Todos los videos
  • Información

Más videos

  • Información
  • Resumen
  • Código
  • Codificación conjunta: Crea una experiencia de texto enriquecida en SwiftUI con AttributedString

    Aprende a crear una experiencia de texto enriquecida con la API TextEditor y AttributedString de SwiftUI. Descubre cómo habilitar la edición de texto enriquecido, crear controles personalizados que manipulen el contenido de tu editor y personalizar las opciones de formato disponibles. Explora las capacidades avanzadas de AttributedString que te ayudarán a crear las mejores experiencias de edición de texto.

    Capítulos

    • 0:00 - Introducción
    • 1:15 - TextEditor y AttributedString
    • 5:36 - Crea controles personalizados
    • 22:02 - Define el formato de tu texto
    • 34:08 - Próximos pasos

    Recursos

    • Character
    • AttributedTextSelection
    • AttributedTextFormatting
    • Building rich SwiftUI text experiences
      • Video HD
      • Video SD

    Videos relacionados

    WWDC25

    • Mejora la experiencia multilingüe de tu app

    WWDC22

    • Get it right (to left)

    WWDC21

    • What's new in Foundation
  • Buscar este video…
    • 1:15 - TextEditor and String

      import SwiftUI
      
      struct RecipeEditor: View {
          @Binding var text: String
      
          var body: some View {
              TextEditor(text: $text)
          }
      }
    • 1:45 - TextEditor and AttributedString

      import SwiftUI
      
      struct RecipeEditor: View {
          @Binding var text: AttributedString
      
          var body: some View {
              TextEditor(text: $text)
          }
      }
    • 4:43 - AttributedString Basics

      var text = AttributedString(
        "Hello 👋🏻! Who's ready to get "
      )
      
      var cooking = AttributedString("cooking")
      cooking.foregroundColor = .orange
      text += cooking
      
      text += AttributedString("?")
      
      text.font = .largeTitle
    • 5:36 - Build custom controls: Basics (initial attempt)

      import SwiftUI
      
      struct RecipeEditor: View {
          @Binding var text: AttributedString
          @State private var selection = AttributedTextSelection()
      
          var body: some View {
              TextEditor(text: $text, selection: $selection)
                  .preference(key: NewIngredientPreferenceKey.self, value: newIngredientSuggestion)
          }
      
          private var newIngredientSuggestion: IngredientSuggestion {
              let name = text[selection.indices(in: text)] // build error
      
              return IngredientSuggestion(
                  suggestedName: AttributedString())
          }
      }
    • 8:53 - Slicing AttributedString with a Range

      var text = AttributedString(
        "Hello 👋🏻! Who's ready to get cooking?"
      )
      
      guard let cookingRange = text.range(of: "cooking") else {
        fatalError("Unable to find range of cooking")
      }
      
      text[cookingRange].foregroundColor = .orange
    • 10:50 - Slicing AttributedString with a RangeSet

      var text = AttributedString(
        "Hello 👋🏻! Who's ready to get cooking?"
      )
      
      let uppercaseRanges = text.characters
        .indices(where: \.isUppercase)
      
      text[uppercaseRanges].foregroundColor = .blue
    • 11:40 - Build custom controls: Basics (fixed)

      import SwiftUI
      
      struct RecipeEditor: View {
          @Binding var text: AttributedString
          @State private var selection = AttributedTextSelection()
      
          var body: some View {
              TextEditor(text: $text, selection: $selection)
                  .preference(key: NewIngredientPreferenceKey.self, value: newIngredientSuggestion)
          }
      
          private var newIngredientSuggestion: IngredientSuggestion {
              let name = text[selection]
      
              return IngredientSuggestion(
                  suggestedName: AttributedString(name))
          }
      }
    • 12:32 - Build custom controls: Recipe attribute

      import SwiftUI
      
      struct IngredientAttribute: CodableAttributedStringKey {
          typealias Value = Ingredient.ID
      
          static let name = "SampleRecipeEditor.IngredientAttribute"
      }
      
      extension AttributeScopes {
          /// An attribute scope for custom attributes defined by this app.
          struct CustomAttributes: AttributeScope {
              /// An attribute for marking text as a reference to an recipe's ingredient.
              let ingredient: IngredientAttribute
          }
      }
      
      extension AttributeDynamicLookup {
          /// The subscript for pulling custom attributes into the dynamic attribute lookup.
          ///
          /// This makes them available throughout the code using the name they have in the
          /// `AttributeScopes.CustomAttributes` scope.
          subscript<T: AttributedStringKey>(
              dynamicMember keyPath: KeyPath<AttributeScopes.CustomAttributes, T>
          ) -> T {
              self[T.self]
          }
      }
    • 12:56 - Build custom controls: Modifying text (initial attempt)

      import SwiftUI
      
      struct RecipeEditor: View {
          @Binding var text: AttributedString
          @State private var selection = AttributedTextSelection()
      
          var body: some View {
              TextEditor(text: $text, selection: $selection)
                  .preference(key: NewIngredientPreferenceKey.self, value: newIngredientSuggestion)
          }
      
          private var newIngredientSuggestion: IngredientSuggestion {
              let name = text[selection]
      
              return IngredientSuggestion(
                  suggestedName: AttributedString(name),
                  onApply: { ingredientId in
                      let ranges = text.characters.ranges(of: name.characters)
      
                      for range in ranges {
                          // modifying `text` without updating `selection` is invalid and resets the cursor 
                          text[range].ingredient = ingredientId
                      }
                  })
          }
      }
    • 17:40 - AttributedString Character View

      text.characters[index] // "👋🏻"
    • 17:44 - AttributedString Unicode Scalar View

      text.unicodeScalars[index] // "👋"
    • 17:49 - AttributedString Runs View

      text.runs[index] // "Hello 👋🏻! ..."
    • 18:13 - AttributedString UTF-8 View

      text.utf8[index] // "240"
    • 18:17 - AttributedString UTF-16 View

      text.utf16[index] // "55357"
    • 18:59 - Updating Indices during AttributedString Mutations

      var text = AttributedString(
        "Hello 👋🏻! Who's ready to get cooking?"
      )
      
      guard var cookingRange = text.range(of: "cooking") else {
        fatalError("Unable to find range of cooking")
      }
      
      let originalRange = cookingRange
      text.transform(updating: &cookingRange) { text in
        text[originalRange].foregroundColor = .orange
        
        let insertionPoint = text
          .index(text.startIndex, offsetByCharacters: 6)
        
        text.characters
          .insert(contentsOf: "chef ", at: insertionPoint)
      }
      
      print(text[cookingRange])
    • 20:22 - Build custom controls: Modifying text (fixed)

      import SwiftUI
      
      struct RecipeEditor: View {
          @Binding var text: AttributedString
          @State private var selection = AttributedTextSelection()
      
          var body: some View {
              TextEditor(text: $text, selection: $selection)
                  .preference(key: NewIngredientPreferenceKey.self, value: newIngredientSuggestion)
          }
      
          private var newIngredientSuggestion: IngredientSuggestion {
              let name = text[selection]
      
              return IngredientSuggestion(
                  suggestedName: AttributedString(name),
                  onApply: { ingredientId in
                      let ranges = RangeSet(text.characters.ranges(of: name.characters))
      
                      text.transform(updating: &selection) { text in
                          text[ranges].ingredient = ingredientId
                      }
                  })
          }
      }
    • 22:03 - Define your text format: RecipeFormattingDefinition Scope

      struct RecipeFormattingDefinition: AttributedTextFormattingDefinition {
          struct Scope: AttributeScope {
              let foregroundColor: AttributeScopes.SwiftUIAttributes.ForegroundColorAttribute
              let adaptiveImageGlyph: AttributeScopes.SwiftUIAttributes.AdaptiveImageGlyphAttribute
              let ingredient: IngredientAttribute
          }
      
          var body: some AttributedTextFormattingDefinition<Scope> {
      
          }
      }
      
      // pass the custom formatting definition to the TextEditor in the updated `RecipeEditor.body`:
      
              TextEditor(text: $text, selection: $selection)
                  .preference(key: NewIngredientPreferenceKey.self, value: newIngredientSuggestion)
                  .attributedTextFormattingDefinition(RecipeFormattingDefinition())
    • 23:50 - Define your text format: AttributedTextValueConstraints

      struct IngredientsAreGreen: AttributedTextValueConstraint {
          typealias Scope = RecipeFormattingDefinition.Scope
          typealias AttributeKey = AttributeScopes.SwiftUIAttributes.ForegroundColorAttribute
      
          func constrain(_ container: inout Attributes) {
              if container.ingredient != nil {
                  container.foregroundColor = .green
              } else {
                  container.foregroundColor = nil
              }
          }
      }
      
      // list the value constraint in the recipe formatting definition's body:
          var body: some AttributedTextFormattingDefinition<Scope> {
              IngredientsAreGreen()
          }
    • 29:28 - AttributedStringKey Constraint: Inherited by Added Text

      static let inheritedByAddedText = false
    • 30:12 - AttributedStringKey Constraint: Invalidation Conditions

      static let invalidationConditions:
        Set<AttributedString.AttributeInvalidationCondition>? =
        [.textChanged]
    • 31:25 - AttributedStringKey Constraint: Run Boundaries

      static let runBoundaries:
        AttributedString.AttributeRunBoundaries? =
        .paragraph
    • 32:46 - Define your text format: AttributedStringKey Constraints

      struct IngredientAttribute: CodableAttributedStringKey {
          typealias Value = Ingredient.ID
      
          static let name = "SampleRecipeEditor.IngredientAttribute"
      
          static let inheritedByAddedText: Bool = false
      
          static let invalidationConditions: Set<AttributedString.AttributeInvalidationCondition>? = [.textChanged]
      }
    • 0:00 - Introducción
    • Esta sección presenta la meta de la sesión: demostrar cómo crear experiencias de edición de texto enriquecidas en SwiftUI usando AttributedString. El presentador, Max, cubrirá tres áreas principales: actualizar TextEditor a fin de admitir texto enriquecido, crear controles personalizados para mejorar el editor y crear una definición de formato de texto personalizado para garantizar un estilo consistente.

    • 1:15 - TextEditor y AttributedString
    • Esta sección se centra en la actualización de un TextEditor de SwiftUI para admitir texto enriquecido cambiando el tipo de datos subyacente de String a AttributedString. Esto permite inmediatamente el soporte para los controles de la IU del sistema para opciones de formato, como negrita, cursiva, tamaño de fuente, colores y Genmoji. TextEditor admite una amplia gama de atributos, incluido el estilo de párrafo. La sección también proporciona un repaso rápido de los conceptos básicos de AttributedString, incluida su estructura (caracteres y ejecuciones de atributos), su naturaleza de tipo de valor, su codificación UTF-8 y su conformidad con los protocolos Swift comunes. Destaca el uso de atributos predefinidos y la posibilidad de definir atributos personalizados.

    • 5:36 - Crea controles personalizados
    • Esta sección explica cómo crear controles personalizados para un TextEditor que interactúen con el resto de la app. Se muestra cómo agregar un botón que permite al usuario marcar el texto seleccionado como ingrediente. Cubre el uso de teclas de preferencia para comunicarse entre el editor y otras partes de la IU. También se profundiza en las complejidades de las selecciones de texto en AttributedString y se explica por qué el tipo AttributedTextSelection usa un RangeSet en lugar de un solo Range para manejar texto bidireccional y selecciones no contiguas. También se destaca el uso de una API de subíndice que porciona un AttributedString con una selección directamente. Finalmente, en la sección se demuestra cómo utilizar un cierre para mutar el texto del editor marcando las ocurrencias del texto seleccionado con un atributo Ingredient personalizado. También se analiza el problema de la selección que se restablece después de mutar la cadena de atributos e introduce el concepto de índices AttributedString y la importancia de actualizarlos después de las mutaciones usando la función de transformación.

    • 22:02 - Define el formato de tu texto
    • Esta sección se centra en el uso del protocolo de definición de formato de texto atribuido para controlar las opciones de formato disponibles en el TextEditor. Se explica cómo crear una definición de formato personalizada que especifica a qué AttributedStringKeys debe responder el editor y se limita a las opciones de formato disponibles. También se presenta AttributedTextValueConstraint para aplicar reglas de formato específicas, como garantizar que los ingredientes siempre estén resaltados en verde. En la sección se explica además cómo restringir los valores de los atributos usando el protocolo AttributedStringKey. Cubre propiedades, como inheritedByAddedText e invalidationConditions, para controlar cómo se heredan e invalidan los atributos durante las mutaciones de texto. Por último, se analiza la propiedad runBoundaries para imponer valores consistentes en todas las secciones de texto, como los párrafos.

    • 34:08 - Próximos pasos
    • En esta sección se proporcionan consejos y recursos finales. Se menciona un proyecto de muestra que enseña las funcionalidades de arrastrar y soltar sin pérdida, exportación RTFD y persistencia de AttributedString con Swift Data. Se destaca que AttributedString es parte del proyecto de código abierto de la fundación Swift y que fomenta las contribuciones. En la sección también se alienta a los desarrolladores a agregar soporte para Genmoji a sus apps con el nuevo TextEditor.

Developer Footer

  • Videos
  • WWDC25
  • Codificación conjunta: Crea una experiencia de texto enriquecida en SwiftUI con AttributedString
  • Open Menu Close Menu
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    • App Store
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • Icon Composer
    • SF Symbols
    Open Menu Close Menu
    • Accessibility
    • Accessories
    • Apple Intelligence
    • Audio & Video
    • Augmented Reality
    • Business
    • Design
    • Distribution
    • Education
    • Games
    • Health & Fitness
    • In-App Purchase
    • Localization
    • Maps & Location
    • Machine Learning & AI
    • Security
    • Safari & Web
    Open Menu Close Menu
    • Documentation
    • Downloads
    • Sample Code
    • Videos
    Open Menu Close Menu
    • Help Guides & Articles
    • Contact Us
    • Forums
    • Feedback & Bug Reporting
    • System Status
    Open Menu Close Menu
    • Apple Developer
    • App Store Connect
    • Certificates, IDs, & Profiles
    • Feedback Assistant
    Open Menu Close Menu
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program
    • Mini Apps Partner Program
    • News Partner Program
    • Video Partner Program
    • Security Bounty Program
    • Security Research Device Program
    Open Menu Close Menu
    • Meet with Apple
    • Apple Developer Centers
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Academies
    • WWDC
    Read the latest news.
    Get the Apple Developer app.
    Copyright © 2026 Apple Inc. All rights reserved.
    Terms of Use Privacy Policy Agreements and Guidelines