How to animate substring in a Text?

Currently, I am using multiple Texts in a horizontal stackview, to achieve animation of substring.

As you can see in the above animation, the text

- conversation
- meeting
- lecture

are animated.

However, there shortcoming of such an approach.

Text size is not consistent among different Text block. The following Text block are having different text size.

- Transform
- conversation/ meeting/ lecture
- to Quick Note

Any idea how we can achieve, so that all text blocks have same text size so that they appear like 1 sentence?

Or, how we can make the text blocks having constant text size, but able to perform line wrapping to next line, so that they appear like 1 sentence?

Currently, this is the code snippet I am using.

import SwiftUI

struct ContentView: View {
    var array = ["lecture", "conversation", "meeting"]
    
    @State var currentIndex : Int = 0
    @State var firstString : String = ""

    var body: some View {

        VStack {
            HStack {
                Text("Transform")
                    .lineLimit(1)
                    .minimumScaleFactor(0.5)
                    .font(.title)
                
               Text(firstString)
                    .lineLimit(1)
                    .minimumScaleFactor(0.5)
                    .font(.title)
                    .transition(AnyTransition.opacity.animation(.easeInOut(duration:1.0)))
                    .background(.yellow)
                
                Text("to Quick Note")
                    .lineLimit(1)
                    .minimumScaleFactor(0.5)
                    .font(.title)
            }.padding()
        }
        .animation(.default)
        .onAppear {
            firstString = array[0]
            
            let timer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true) { _ in
                if currentIndex == array.count - 1 {
                    self.firstString = array[0]
                    currentIndex = 0
                }
                else {
                    self.firstString = array[currentIndex+1]
                    currentIndex += 1
                }
            }
        }
    }
}

#Preview {
    ContentView()
}
Answered by yccheok in 818828022

The issue is solved by using the following code.

VStack {
    HStack {
        Text("Transform")

        Text(array[currentIndex])
            .background(.yellow)

        Text("to Quick Note")
    }
    .lineLimit(1)
    .font(.title)
    .minimumScaleFactor(0.5)
    .scaledToFit()
    .padding()
}
.animation(.default, value: currentIndex)
.task(id: currentIndex) {
    try? await Task.sleep(for: .seconds(2))
    if currentIndex == array.count - 1 {
        currentIndex = 0
    } else {
        currentIndex += 1
    }
}

Reference : https://stackoverflow.com/a/79282178/72437

What do you want ?

  • conversation/ meeting/ lecture to have always the same size ?
  • Have the whole string with the same size ?

For the later I tried using scaleEffect, which seems to be giving the expected result.

struct ContentView: View {
    var array = ["lecture", "conversation", "meeting"]
    var scaleEffects = [1.0, 0.9, 1.0]  // <<-- ADDED : firstString forces scale down
    
    @State var currentIndex : Int = 0
    @State var firstString : String = ""
    @State var scale = 1.0  // <<-- ADDED

    var body: some View {

        VStack {
            HStack {
                Text("Transform")
                    .lineLimit(1)
                    .minimumScaleFactor(0.5)
                    .font(.title)
                
               Text(firstString)
                    .lineLimit(1)
                    .minimumScaleFactor(0.5)
                    .font(.title)
                    .transition(AnyTransition.opacity.animation(.easeInOut(duration:1.0)))
                    .background(.yellow)
                
                Text("to Quick Note")
                    .lineLimit(1)
                    .minimumScaleFactor(0.5)
                    .font(.title)
            }
            .padding()
            .frame(width: 420)
            .scaleEffect(x: scale, y: scale)  // <<-- ADDED
        }
        .animation(.default)
        .onAppear {
            firstString = array[0]
            scale = scaleEffects[0]  // <<-- ADDED
            let timer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true) { _ in
                if currentIndex == array.count - 1 {
                    self.firstString = array[0]
                    self.scale = scaleEffects[0]  // <<-- ADDED
                    currentIndex = 0
                }
                else {
                    self.firstString = array[currentIndex+1]
                    self.scale = scaleEffects[currentIndex+1]  // <<-- ADDED
                    currentIndex += 1
                }
            }
        }
    }
}
Accepted Answer

The issue is solved by using the following code.

VStack {
    HStack {
        Text("Transform")

        Text(array[currentIndex])
            .background(.yellow)

        Text("to Quick Note")
    }
    .lineLimit(1)
    .font(.title)
    .minimumScaleFactor(0.5)
    .scaledToFit()
    .padding()
}
.animation(.default, value: currentIndex)
.task(id: currentIndex) {
    try? await Task.sleep(for: .seconds(2))
    if currentIndex == array.count - 1 {
        currentIndex = 0
    } else {
        currentIndex += 1
    }
}

Reference : https://stackoverflow.com/a/79282178/72437

How to animate substring in a Text?
 
 
Q