UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

How to insert other views into text

Forums > SwiftUI

Hi,

What's the best way to insert another view into text in SwiftUI?

I know inserting images is simple (https://www.hackingwithswift.com/quick-start/swiftui/how-to-insert-images-into-text), but I would like to insert a custom view (in my case: circles with letters in them).

Thanks!

3      

It's a bit incorrect to say you can simply insert an Image into a Text and have it scale with the Text. You can insert an Image made from an SFSymbol into a Text and have it scale, but not just any old Image.

Luckily, SFSymbols contains a series of symbols that are circles with letters in them. You can do something like this: Text("Hello ") + Text(Image(systemName: "a.circle")) + Text(" World!") and so on.

If that doesn't suit your need, you will have to approach it differently.

Or if you don't care about the image scaling with the text, then something like this will work just fine:

Text("Hello ") + Text(Image(uiImage: testImage)) + Text(" World!")

Be aware, though, that my testImage has a size of 44x44. When I tried to use a large image and scale it down using resizable and frame, I got a compiler error.

3      

Hey, great answer from @roosterboy, just to add on: the reason that downscaling an image using the resizable and frame modifiers throws a compiler error is that the Text struct has initializers for localized strings, StringProtocol, a bunch of different date-related structs, and Image. That last one's important: there is no Text initializer that would accept any SwiftUI View, just those specific types. When you apply the resizable modifier to an Image, the return type of the modifier is another Image struct, so passing in just Image(...).resizable into the Text initializer would work. But the frame modifier has an opaque some View return type, meaning that it's no longer a pure Image and so Text doesn't accept it.

There's a way around this, and that's HStack. You could do something like:

HStack(alignment: .firstTextBaseline) {
    Text("Hello ")
    Image(uiImage: testImage)
        .resizable()
        .frame(width: 44, height: 44)
    Text(" World!")
}

Unfortunately that will only work for single-line text. If you try it with multiline text you'll find that it creates three different columns, and it won't look the way you want anymore.

3      

Thank you for your answers. Unfortunately, these solutions won't fully fix my problem.

What I would like to achieve is text with any View so that the View (in my case a circle with a letter in it) can be animated (the colour can change).

What I'm really looking for is the equivalent of Flutter's WidgetSpan: https://api.flutter.dev/flutter/widgets/WidgetSpan-class.html.

I know a similar solution exists for native Android where Spans can be used.

I think I'll experiment with this over the weekend and try to post my solution.

I suppoe to solve my particular case I could use UIGraphicsImageRenderer, but this might not be ideal for everyone as drawing text manually requires sizing and it would be impossible to make the drawn text wrap with other text. My requirement is to only draw a single letter, so it seems OK.

3      

Something like this wouldn't work for you?

struct SimpleTestView: View {

    @State private var viewColor: Color = .green

    var body: some View {
        VStack(spacing: 20) {
            (Text("Hello ") +
                Text(Image(systemName: "a.circle")).foregroundColor(viewColor) +
                Text(" World!"))
                .font(.largeTitle)

            Button(action: {
                withAnimation {
                    viewColor = viewColor == .green ? .purple : .green
                }
            }) {
                Text("Change color")
            }
        }
    }
}

struct SimpleTestView_Previews: PreviewProvider {
    static var previews: some View {
        SimpleTestView()
    }
}

change view color

Even if you don't want to use SFSymbols for the letter in a circle image, you could probably accomplish the same thing by applying .withRenderingMode(.alwaysTemplate) to a UIImage and then build your SwiftUI Image from there.

But you know your code so maybe this won't work. But I wanted to throw it out there just in case.

Hmm, on second thought this doesn't seem to be animating, despite the withAnimation block.

So do you need it to simply change color like in the first example, or actually to animate like in this example?

change view color 2

This one was done by setting the foregroundColor to .white and then using the colorMultiply() modifier with the intended color inside, like so:

struct SimplerTestView: View {

    @State private var color = Color.red

    var body: some View {
        Text("Animate Me!")
            .font(.largeTitle)
            .foregroundColor(Color.white)
            .colorMultiply(color)
            .onTapGesture {
                withAnimation(.easeInOut(duration: 1)) {
                    color = color == .red ? .blue : .red
                }
            }
    }

}

But it won't work when concatenating Texts together since it changes the View's type from Text.

3      

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.