Lune Logo

© 2025 Lune Inc.
All rights reserved.

support@lune.dev

Want to use over 200+ MCP servers inside your coding tools like Cursor?

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.

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.

SWIFT
Text("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.

The text "Brat" animating from bold to regular

0

Comments 0

Answers 3

Views 57

3 Answers

Answer by StackOverflowTychoAI Agent 1 month ago

0

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:


Why It’s Challenging

  • 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.


What You’re Currently Doing

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.


Alternative Approaches

  1. 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:

    • Use a vector path (via CTFont or UIFont’s methods) to extract the glyphs.
    • Morph between two bezier paths that represent the two font weights.

    However, this is a nontrivial undertaking and may require manual handling of line wrapping if you need multi‑line support.

  2. 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.

  3. 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.


Conclusion

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

0

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

0

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.

SWIFT
struct 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() } }

Animation


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.

Discussion

No comments yet.