MarqueeText is now layout-interchangeable with a single-line Text, and meaningfully cheaper per row in long lists.
Layout parity with Text
The view reported a hard-coded 20pt height on its first layout pass, so any font whose line height was not 20pt resized one frame after appearing. In a List or LazyVStack that showed up as row jitter and scroll-offset drift. Sizing now comes from the layout, so it is correct on the first pass and never changes as measurement settles.
Also fixed:
- A generous height proposal no longer stretches the view. It behaved like
ColorinsideZStack, overlays and stacks with taller siblings; it now behaves likeText. - Glyph overhang is no longer clipped. Arabic diacritics, emoji and tall accents legitimately paint outside the typographic line box, and
Textrenders them. Clipping is now horizontal only. - Overflowing text that cannot scroll — most importantly under Reduce Motion — now truncates with an ellipsis instead of being cut off mid-glyph.
- Text baselines are forwarded, so
HStack(alignment: .firstTextBaseline)lines up correctly.
These guarantees are covered by tests that compare MarqueeText against a real Text in a hosting view, across multiple fonts, both layout directions, and every size proposal.
Performance
The view used to lay its text out twice — once hidden, purely to measure it — and ran a second geometry reader to read the container width. Both values are already known to the layout, so it now reports them by proposing them as the size of a single weightless probe.
Measured over 200 rows:
| before | after | |
|---|---|---|
| overflowing row | 0.755 ms | 0.577 ms |
| row that fits | 0.429 ms | 0.264 ms |
Text that fits still costs nothing at rest, and in List or LazyVStack per-frame work stays flat whether the collection holds 25 rows or 2,500. Note that a non-lazy VStack inside a ScrollView builds every row, so offscreen marquees keep animating there — use List or LazyVStack for long collections.
watchOS 9 support
watchOS is now declared in Package.swift. It previously worked only by SPM inferring a floor from the APIs used, which meant consumers below watchOS 9 failed with a confusing module-level error instead of a clean resolution failure.
Other
LocalizedStringResourcecontent is compared by value instead of through aString(describing:)reflection dump on every body evaluation.- The internal measurement preference can no longer silently drop a measurement, and clears correctly when the text becomes empty.
- Added CI: lint, tests with coverage, and a build matrix across all five declared platforms.
Full Changelog: 1.1.0...1.2.0