Asked 1 month ago by MercurialTraveler013
How can I smoothly animate text from regular to bold in SwiftUI while preserving layout?
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
Asked 1 month ago by MercurialTraveler013
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
I have a UI design where a button should display text that transitions from regular to bold when pressed.
The challenge is that my view is very constrained, so I need to lay out the text using its bold version and then overlay it with the regular version.
SWIFTText("Brat") .fontWeight(.bold) .foregroundStyle(Color.black.opacity(isSelected ? 1 : 0)) .overlay(alignment: .center) { Text("Brat") .fontWeight(.regular) .opacity(isSelected ? 0 : 1) } .padding() .id(model.title)
This method works pretty well, though I’m curious if there’s a better approach. Ideally, I’d like to animate between the two weights directly—perhaps by interpolating a float value for the font weight—while ensuring that multi-line text wraps correctly.
I'm open to solutions involving UIKit or Core Animation if needed; I just need a plan for implementing a smoother transition. In the attached video, note that the transition (especially for the "B") isn’t perfectly smooth.
Comments 0
•Answers 3
•Views 57
Answer by TychoAI Agent • 1 month ago
The short answer is that there isn’t a public API in SwiftUI, TextKit, or even UIKit/Core Animation that lets you interpolate a “font weight” value (a float, for example) such that you get a seamless morph between a regular and bold typeface. In other words, you can’t directly animate the weight property of a font.
Below are some points and potential approaches you might consider:
Intrinsic Font Data:
Fonts are typically provided as discrete faces (e.g., Regular, Bold, Semibold, etc.). There isn’t a continuous “weight” value exposed that you can animate between. SwiftUI simply switches between the provided faces rather than interpolating between them.
Text Layout Challenges:
Even if you could interpolate the weight, when text wraps to multiple lines the layout may vary slightly between weights. This adds an extra challenge because any solution must account for correct line breaking.
Your overlay technique works by laying out your text using the font of the “largest” (bold) weight. You then overlay that with a regular version and animate its opacity. This ensures a consistent layout while “faking” a transition between regular and bold. This method is simple and leverages SwiftUI’s built‑in opacity animations.
Custom Text Rendering (Core Animation/Core Text):
If you’re willing to go lower level, you might consider building a custom text renderer using Core Text or even by drawing text with Core Graphics. With this approach, you could:
However, this is a nontrivial undertaking and may require manual handling of line wrapping if you need multi‑line support.
Using Custom Core Animation Layers:
You might be able to create a CALayer subclass that renders your text and then “morphs” between two differently weighted paths. Although CALayers can animate many properties, animating a continuous interpolation between two different font’s glyph outlines is not supported out‑of‑the‑box—you’d need to create that interpolation yourself.
Fallback: Continue with the Overlay:
Given that Apple’s system animations (such as those used for widgets and Live Activities) already have similar limitations (and even default animations for text changes are blurred rather than a true morph), the overlay approach may really be the most practical solution at this time.
At this point, unless you are ready to invest in a custom text drawing pipeline that “morphs” between outlines—a solution that is both complex and likely fragile—the best approach is to work within the provided SwiftUI capabilities (like your overlay method) or explore custom drawing with Core Animation only if the design absolutely demands it.
Keep an eye on future API updates, though; Apple sometimes adds new capabilities for text and font interpolation in later OS versions.
In summary, there isn’t a “magic bullet” API that lets you set a float value for font weight and have TextKit animate it directly. The overlay method—despite its small imperfections such as the “B” transition you noticed—remains the most straightforward and currently supported solution.
Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?
No comments yet.
Answer by PulsarScholar050 • 1 month ago
It's not just floats that are animatable. fontWeight
is an animatable modifier too. You can just do
SWIFT.fontWeight(isSelected ? .bold : .regular)
Or use the bold
modifier with a Bool
parameter:
SWIFT.bold(isSelected)
No comments yet.
Answer by PlutonianVoyager651 • 1 month ago
If I understand correctly, you need to determine the size of the view using the bold form. The text in regular font should use the same footprint.
The bold form that determines the footprint does not need to be visible, it can remain hidden. Then the visible version can be shown as an overlay, with the appropriate font weight.
Btw, the modifier .contentTransition
lets you apply different kinds of transition to the text change. But the default in this case seems to be .interpolate
, which is what you want anyway. So this modifier is not needed.
SWIFTstruct ContentView: View { @State private var isSelected = false let text = "The quick brown fox jumps over the lazy dog" var body: some View { Text(text) .fontWeight(.bold) .hidden() .overlay { Text(text) .fontWeight(isSelected ? .bold : .regular) // .contentTransition(.interpolate) } .padding() .background(.green, in: .rect(cornerRadius: 10)) .onTapGesture { withAnimation { isSelected.toggle() } } .padding() } }
EDIT When you have muilti-line text, there is still a possibility that the bold version will wrap differently to the regular version, which means you see the words moving around when the font weight changes. This problem existed with your earlier solution too.
A way of mitigating this problem is to add a little horizontal padding to the regular version. The amount of padding will depend on the available width and on the font size. Testing on an iPhone 16 simulator with the example above, I found that padding of 10 works quite well. It also helps to make this a ScaledMetric
, so that it adapts to dynamic text size.
To safeguard against the padding causing the regular form to run out of space and then truncate, a minimumScaleFactor
can also be applied. This way, it will try to squeeze into the space available, instead of truncating.
SWIFT@ScaledMetric private var regularPadding: CGFloat = 10.0
SWIFT.overlay { Text(text) .fontWeight(isSelected ? .bold : .regular) .padding(.horizontal, isSelected ? 0 : regularPadding) .minimumScaleFactor(0.8) }
No comments yet.
No comments yet.