[Pitch] AttributeContainer Filtering

Hi all! I have another pitch for a small new set of APIs on the AttributeContainer type to allow clients to filter the contained attributes based on a limited set of criteria. Feel free to read below and let me know if you have any comments/questions!


AttributeContainer Filtering

  • Proposal: SF-NNNN
  • Authors: Jeremy Schonfeld
  • Review Manager: TBD
  • Status: Pitch
  • Implementation: Awaiting Implementation

Introduction/Motivation

In a prior pitch we added new advanced attribute behaviors to AttributedString which allow attributes to declare how they behave/change across various AttributedString operations. Today these behaviors include:

  1. Declaring attributes bound to the full length of paragraphs/characters (ensuring consistent values across the full range of the paragraph/character)
  2. Declaring attributes that are invalidated and removed when other attributes or text content change
  3. Declaring attributes that should not be included when adding new text to the end of a run

Developers define these behaviors on each attribute's AttributedStringKey type, but once the attribute values are stored in an AttributedString/AttributeContainer, developers do not have the ability to inspect the behaviors of the stored attributes dynamically. When using AttributedString to store text for more advanced applications such as a text editor, it becomes necessary to create higher level APIs (in modules beyond Foundation) that require knowledge of these behaviors. For example, a text editor that provides APIs for retrieving the "typing attributes" (attributes at the current cursor position) might want to exclude attributes that aren't inherited by additional text. Additionally, an app may wish to provide an API that exposes just the paragraph-bound attributes at a particular location. To support these use cases, we propose adding new APIs to AttributeContainer in order to create a "filtered" container with a subset of its attributes based on these behaviors.

Proposed solution

Given the example above of a text editor getting the "typing attributes" at a current location, the developer might use these new APIs like the following:

func typingAttributes(in text: AttributedString, selection: UserTextSelection) -> AttributeContainer {
    if selection.isSingleCursor {
        return text.runs[selection.index].attributes.filter(inheritedByAddedText: true)
    } else {
        return text.runs[selection.startIndex].attributes.filter(inheritedByAddedText: true)
    }
}

Note: UserTextSelection here is for demonstration purposes only (not proposed API) and represents a simplified selection that could be at a single location or a range of locations.

In this case, the developer's API finds the specified run based on a provided user selection and retrieves its attributes via the .attributes property on AttributedString.Runs.Run. The developer can now use this new API to filter that AttributeContainer to only those attributes that should be inherited by added text (and thus are attributes that you might see in the text editor's selection UI). The developer could similarly call an API like .filter(runBoundaries: .paragraph) to filter to only paragraph-level attributes


For the full proposal including the detailed design and alternatives considered, check out the full proposal document on the swift-foundation repo.

2 Likes

[Accepted] Given this is a fairly short enhancement, and all of my questions are covered in the Alternatives Considered section, I'd like to use this pitch as an abbreviated review and accept it.

1 Like