Home > Blockchain >  How to make marquee text cut at a certain point and come from the other side again?
How to make marquee text cut at a certain point and come from the other side again?

Time:09-13

I created a musicplayer UI and want to make it responsive. When the song title is too long I want it to be an marquee text scrolling from the leading of the VStack to trailing side. Once the text reached the trailing side it should cut and appear from the leading side again. Currently it just goes outside of the VStack and then starts the animation over again. enter image description here

This is my code:

@State var scrollText = false
VStack(alignment: .leading) {
  VStack {
     Text(album.songs.title ?? "").font(.system(size: 13))
     .foregroundColor(Color.white).fixedSize()
     .offset(x: scrollText ? 40 : 0)
     .animation(Animation.linear(duration: 4)
     .repeatForever(autoreverses: false))
     .onAppear() {
                   scrollText.toggle()
                 }
        }.frame(width: 100, height: 18, alignment: .leading)
         .background(.red)
    Text(album.name).font(.system(size: 9)).foregroundColor(Color.gray)
     }

CodePudding user response:

The reason it goes outside of VStack because you set a const which is 40 to .offset. What you should .offset here is variable = width of cell - text.size.width ( depends on size of width text inside)

Code will be like this

struct ContentView: View {
    @State var scrollText = false
    @State var sizeOfText: CGSize = CGSize(width: 0, height: 0)

    var body: some View {
        VStack(alignment: .leading) {
          VStack {
              Text("abc").font(.system(size: 13))
             .foregroundColor(Color.white).fixedSize()
             .background(GeometryReader { (geometryProxy : GeometryProxy) in
                             HStack {}
                             .onAppear {
                                 sizeOfText = geometryProxy.size
                             }
                         })
             .offset(x: scrollText ? 100 - sizeOfText.width : 0)
             .animation(Animation.linear(duration: 4)
             .repeatForever(autoreverses: false))
             .onAppear() {
                scrollText.toggle()
             }
        }.frame(width: 100, height: 18, alignment: .leading)
        .background(.red)
        Text("album.net").font(.system(size: 9)).foregroundColor(Color.gray)
        }
    }
}

The result: Result

CodePudding user response:

This is how I did it:

struct MarqueeText: View {
    @State var text: String
    @State var storedSize: CGSize = .zero
    @State var offset: CGFloat = 30
    var font: UIFont
    var animationSpeed: Double = 0.04
    var delayTime: Double = 0
    
    func textSize() -> CGSize {
        let attributes = [NSAttributedString.Key.font: font]
        let size = (text as NSString).size(withAttributes: attributes)
        return size
    }
    
    var body: some View {
        ScrollView(.horizontal, showsIndicators: false){
            Text(text)
                .foregroundColor(Color.white)
                .font(Font(font))
                .offset(x: offset)
                .padding(.horizontal, 15)
        }
        .disabled(true)
        .onAppear() {
            let baseText = text
            (1...10).forEach { _ in
                storedSize = textSize()
                text.append(" ")
            }
            text.append(baseText)
            storedSize = textSize()
            let timing: Double = (animationSpeed * storedSize.width)
            DispatchQueue.main.asyncAfter(deadline: .now()   delayTime) {
                
                withAnimation(.linear(duration: timing)) {
                    offset = -storedSize.width
                }
            }
        }
        .onReceive(Timer.publish(every: (animationSpeed * storedSize.width)   delayTime, on: .main, in: .default).autoconnect()) { _ in
            offset = -15
            withAnimation(.linear(duration: (animationSpeed * storedSize.width))) {
                offset = -storedSize.width   20
            }
        }
    }
}

In my ContentView I check for the length of the text

func textSize(font: UIFont, text: String) -> CGSize {
        let attributes = [NSAttributedString.Key.font: font]
        let size = (text as NSString).size(withAttributes: attributes)
        return size
    }

if 
textSize(font: UIFont.systemFont(ofSize: 13, weight: .bold), text: song.title).width 
>= 66 { // i check this value by just printing the result of the function
     MarqueeText(text: song.title, font: UIFont.boldSystemFont(ofSize: 13))
} else { 
Text(song.title)
.font(.system(size: 13)).foregroundColor(Color.white)
                                }
  • Related