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.
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)
}
}
}
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)
}