Skip to content

Instantly share code, notes, and snippets.

@Chronos2500
Created May 13, 2025 15:19
Show Gist options
  • Save Chronos2500/688b2bb7fad02022c53d033d7e2517d5 to your computer and use it in GitHub Desktop.
Save Chronos2500/688b2bb7fad02022c53d033d7e2517d5 to your computer and use it in GitHub Desktop.
MarqueeLabel Please be fully aware that these use Private APIs.
let label = UILabel()
label.text = "Swift is a modern, intuitive programming language crafted for all Apple platforms."
label.setValue(true, forKey: "marqueeEnabled")
label.setValue(true, forKey: "marqueeRunning")
label.setValue(0, forKey: "marqueeRepeatCount")
//
// MarqueeLabel.swift
// PrivateAPISample
//
// Created by Chronos2500 on 2025/05/13.
//
import SwiftUI
fileprivate let text = """
Swift is a modern, intuitive programming language crafted for all Apple platforms.
"""
struct MarqueeLabelView: View {
var body: some View {
MarqueeLabel(
text,
font: .preferredFont(forTextStyle: .title1)
)
}
}
struct MarqueeLabel: UIViewRepresentable {
var text: String
var repeatCount: Int
var font: UIFont
var textColor: UIColor
init(
_ text: String,
repeatCount: Int = 0,
font: UIFont = .preferredFont(forTextStyle: .body),
textColor: UIColor = .label
) {
self.text = text
self.repeatCount = repeatCount
self.font = font
self.textColor = textColor
}
func makeUIView(context: Context) -> UILabel {
let label = UILabel()
label.text = text
label.textAlignment = .natural
label.font = font
label.textColor = textColor
label.backgroundColor = .clear
label.numberOfLines = 1
label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
label.setContentHuggingPriority(.defaultLow, for: .horizontal)
label.setContentHuggingPriority(.required, for: .vertical)
label.setContentCompressionResistancePriority(.required, for: .vertical)
label.adjustsFontForContentSizeCategory = true
label.setValue(true, forKey: "marqueeEnabled")
label.setValue(true, forKey: "marqueeRunning")
label.setValue(repeatCount, forKey: "marqueeRepeatCount")
label.setValue(40, forKey: "marqueeLoopPadding")
label.setValue(true, forKey: "marqueeUpdatable")
return label
}
func updateUIView(_ uiView: UILabel, context: Context) {
}
}
#Preview {
MarqueeLabelView()
}
@Chronos2500
Copy link
Author

Chronos2500 commented May 13, 2025

  • Setting marqueeRepeatCount to 0 will cause the marquee effect to repeat indefinitely. If a positive integer is specified, the marquee will stop after completing the specified number of repetitions.
  • By default, after completing one full scroll, the marquee pauses for a certain duration before repeating.
  • When the marquee effect is active, both ends of the content are automatically masked with a fading gradient effect.
  • By default, the marquee effect is not triggered if the content width is shorter than the view’s width.
MarqueeLabel.mov

@llsc12
Copy link

llsc12 commented May 17, 2025

Any way to make them pause to wait for another label to finish? Kinda like in apple music

@Chronos2500
Copy link
Author

Any way to make them pause to wait for another label to finish? Kinda like in apple music

@llsc12
To temporarily stop the marquee effect, execute setValue(false, forKey: "marqueeRunning").
In SwiftUI, it’s recommended to use a @ Binding variable and update the state within the updateUIView function.

@llsc12
Copy link

llsc12 commented May 17, 2025

@llsc12 To temporarily stop the marquee effect, execute setValue(false, forKey: "marqueeRunning"). In SwiftUI, it’s recommended to use a @ Binding variable and update the state within the updateUIView function.

@Chronos2500 how would i know when to stop one marquee label to wait for the other? presumably i'd need to know when the last label has finished before i can then tell all labels to run again right? thanks.

@LeoNatan
Copy link

LeoNatan commented Sep 7, 2025

@llsc12 I'm just looking at the internal impl. Apple has an internal _UILabelMarqueeAnimationDelegate object which listens to animation start and end. So you would set [self setValue:@1 forKey:@"marqueeRepeatCount"]; in all your labels, listen to animationDidStop:finished: in the delegate object of the longest running label, and notify other labels to start animation again.

The animation object contains the animation length, but not sure yet how UILabel calculates it. It's a relatively simple calc, but params such as rate are not exposed. Once I have all this implemented, I will post a small framework with an easy public API for what you need.

@llsc12
Copy link

llsc12 commented Sep 7, 2025

Very interesting, also thank you for your insights!

@LeoNatan
Copy link

LeoNatan commented Sep 8, 2025

You can play around with what I came up with here:

https://github.com/LeoNatan/LNSystemMarqueeLabel

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment