iTunes 歌曲标题在 Cocoa 中滚动

发布于 2024-09-09 03:54:42 字数 503 浏览 3 评论 0原文

我进行了广泛的搜索,但我无法找到任何有关如何在 Cocoa 中文本太大的情况下实现与 iTunes 歌曲标题滚动类似的效果的信息。我尝试在 NSTextField 上设置边界但无济于事。我尝试过使用 NSTextView 以及使用 NSScrollView 的各种尝试。我确信我错过了一些简单的东西,但任何帮助将不胜感激。如果可能的话,我也希望不必使用 CoreGraphics。

示例,注意“Base. FM http://www。"文本已滚动。如果您需要更好的示例,请打开 iTunes,其中包含一首标题相当大的歌曲,然后观看它来回滚动。

我认为肯定有一种简单的方法可以使用 NSTextField 和 NSTimer 创建选取框类型效果,但可惜。

I have searched extensively and cannot for the life of me find any information about how to achieve a similar effect to that of the iTunes song title scrolling if the text is too large in Cocoa. I have tried setting the bounds on a NSTextField to no avail. I have tried using NSTextView as well as various attempts at using NSScrollView. I am sure I am missing something simple but any help would be greatly appreciated. I am also hoping to not have to use CoreGraphics if at all possible.

Example, notice the "Base.FM http://www." text has been scrolled. If you need a better example open iTunes with a song with a rather large title and watch it scroll back and forth.

I would think surely there is a simple way to just create a marquee type effect with an NSTextField and an NSTimer, but alas.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

撩起发的微风 2024-09-16 03:54:42

我可以看到,如果您试图将功能硬塞到现有控件中,这将是多么困难。然而,如果你只是从一个普通的 NSView 开始,那也没有那么糟糕。我在大约 10 分钟内完成了这个...

//ScrollingTextView.h:
#import <Cocoa/Cocoa.h>
@interface ScrollingTextView : NSView {
    NSTimer * scroller;
    NSPoint point;
    NSString * text;
    NSTimeInterval speed;
    CGFloat stringWidth;
}

@property (nonatomic, copy) NSString * text;
@property (nonatomic) NSTimeInterval speed;

@end


//ScrollingTextView.m

#import "ScrollingTextView.h"

@implementation ScrollingTextView

@synthesize text;
@synthesize speed;

- (void) dealloc {
    [text release];
    [scroller invalidate];
    [super dealloc];
}

- (void) setText:(NSString *)newText {
    [text release];
    text = [newText copy];
    point = NSZeroPoint;

    stringWidth = [newText sizeWithAttributes:nil].width;

    if (scroller == nil && speed > 0 && text != nil) {
        scroller = [NSTimer scheduledTimerWithTimeInterval:speed target:self selector:@selector(moveText:) userInfo:nil repeats:YES];
    }
}

- (void) setSpeed:(NSTimeInterval)newSpeed {
    if (newSpeed != speed) {
        speed = newSpeed;

        [scroller invalidate];
        scroller == nil;
        if (speed > 0 && text != nil) {
            scroller = [NSTimer scheduledTimerWithTimeInterval:speed target:self selector:@selector(moveText:) userInfo:nil repeats:YES];
        }
    }
}

- (void) moveText:(NSTimer *)timer {
    point.x = point.x - 1.0f;
    [self setNeedsDisplay:YES];
}

- (void)drawRect:(NSRect)dirtyRect {
    // Drawing code here.

    if (point.x + stringWidth < 0) {
        point.x += dirtyRect.size.width;
    }

    [text drawAtPoint:point withAttributes:nil];

    if (point.x < 0) {
        NSPoint otherPoint = point;
        otherPoint.x += dirtyRect.size.width;
        [text drawAtPoint:otherPoint withAttributes:nil];
    }
}

@end

只需将 NSView 拖到 Interface Builder 中的窗口上并将其类更改为“ScrollingTextView”即可。然后(在代码中),您可以:

[myScrollingTextView setText:@"This is the text I want to scroll"];
[myScrollingTextView setSpeed:0.01]; //redraws every 1/100th of a second

这显然是相当基本的,但它可以完成您正在寻找的内容,并且是一个不错的起点。

I can see how this would be difficult if you're trying to shoehorn the functionality into an exist control. However, if you just start with a plain NSView, it's not that bad. I whipped this up in about 10 minutes...

//ScrollingTextView.h:
#import <Cocoa/Cocoa.h>
@interface ScrollingTextView : NSView {
    NSTimer * scroller;
    NSPoint point;
    NSString * text;
    NSTimeInterval speed;
    CGFloat stringWidth;
}

@property (nonatomic, copy) NSString * text;
@property (nonatomic) NSTimeInterval speed;

@end


//ScrollingTextView.m

#import "ScrollingTextView.h"

@implementation ScrollingTextView

@synthesize text;
@synthesize speed;

- (void) dealloc {
    [text release];
    [scroller invalidate];
    [super dealloc];
}

- (void) setText:(NSString *)newText {
    [text release];
    text = [newText copy];
    point = NSZeroPoint;

    stringWidth = [newText sizeWithAttributes:nil].width;

    if (scroller == nil && speed > 0 && text != nil) {
        scroller = [NSTimer scheduledTimerWithTimeInterval:speed target:self selector:@selector(moveText:) userInfo:nil repeats:YES];
    }
}

- (void) setSpeed:(NSTimeInterval)newSpeed {
    if (newSpeed != speed) {
        speed = newSpeed;

        [scroller invalidate];
        scroller == nil;
        if (speed > 0 && text != nil) {
            scroller = [NSTimer scheduledTimerWithTimeInterval:speed target:self selector:@selector(moveText:) userInfo:nil repeats:YES];
        }
    }
}

- (void) moveText:(NSTimer *)timer {
    point.x = point.x - 1.0f;
    [self setNeedsDisplay:YES];
}

- (void)drawRect:(NSRect)dirtyRect {
    // Drawing code here.

    if (point.x + stringWidth < 0) {
        point.x += dirtyRect.size.width;
    }

    [text drawAtPoint:point withAttributes:nil];

    if (point.x < 0) {
        NSPoint otherPoint = point;
        otherPoint.x += dirtyRect.size.width;
        [text drawAtPoint:otherPoint withAttributes:nil];
    }
}

@end

Just drag an NSView onto your window in Interface Builder and change its class to "ScrollingTextView". Then (in code), you do:

[myScrollingTextView setText:@"This is the text I want to scroll"];
[myScrollingTextView setSpeed:0.01]; //redraws every 1/100th of a second

This is obviously pretty rudimentary, but it does the wrap around stuff that you're looking for and is a decent place to start.

活雷疯 2024-09-16 03:54:42

对于任何在 Swift 4 中寻找此内容的人,我已经转换了 Dave 的答案并添加了更多功能。

import Cocoa

open class ScrollingTextView: NSView {
    /// Text to scroll
    open var text: NSString?

    /// Font for scrolling text
    open var font: NSFont?

    /// Scrolling text color
    open var textColor: NSColor = .headerTextColor

    /// Determines if the text should be delayed before starting scroll
    open var isDelayed: Bool = true

    /// Spacing between the tail and head of the scrolling text
    open var spacing: CGFloat = 20

    /// Amount of time the text is delayed before scrolling
    open var delay: TimeInterval = 2 {
        didSet {
            updateTraits()
        }
    }

    /// Length of the scrolling text view
    open var length: CGFloat = 0 {
        didSet {
            updateTraits()
        }
    }

    /// Speed at which the text scrolls. This number is divided by 100.
    open var speed: Double = 4 {
        didSet {
            updateTraits()
        }
    }

    private var timer: Timer?
    private var point = NSPoint(x: 0, y: 0)
    private var timeInterval: TimeInterval?

    private(set) var stringSize = NSSize(width: 0, height: 0) {
        didSet {
            point.x = 0
        }
    }

    private var timerSpeed: Double? {
        return speed / 100
    }

    private lazy var textFontAttributes: [NSAttributedString.Key: Any] = {
        return [NSAttributedString.Key.font: font ?? NSFont.systemFont(ofSize: 14)]
    }()

    /**
     Sets up the scrolling text view
     - Parameters:
     - string: The string that will be used as the text in the view
     */
    open func setup(string: String) {
        text = string as NSString
        stringSize = text?.size(withAttributes: textFontAttributes) ?? NSSize(width: 0, height: 0)
        setNeedsDisplay(NSRect(x: 0, y: 0, width: frame.width, height: frame.height))
        updateTraits()
    }
}

private extension ScrollingTextView {
    func setSpeed(newInterval: TimeInterval) {
        clearTimer()
        timeInterval = newInterval

        guard let timeInterval = timeInterval else { return }
        if timer == nil, timeInterval > 0.0, text != nil {
            if #available(OSX 10.12, *) {
                timer = Timer.scheduledTimer(timeInterval: newInterval, target: self, selector: #selector(update(_:)), userInfo: nil, repeats: true)

                guard let timer = timer else { return }
                RunLoop.main.add(timer, forMode: .commonModes)
            } else {
                // Fallback on earlier versions
            }
        } else {
            clearTimer()
            point.x = 0
        }
    }

    func updateTraits() {
        clearTimer()

        if stringSize.width > length {
            guard let speed = timerSpeed else { return }
            if #available(OSX 10.12, *), isDelayed {
                timer = Timer.scheduledTimer(withTimeInterval: delay, repeats: false, block: { [weak self] timer in
                    self?.setSpeed(newInterval: speed)
                })
            } else {
                setSpeed(newInterval: speed)
            }
        } else {
            setSpeed(newInterval: 0.0)
        }
    }

    func clearTimer() {
        timer?.invalidate()
        timer = nil
    }

    @objc
    func update(_ sender: Timer) {
        point.x = point.x - 1
        setNeedsDisplay(NSRect(x: 0, y: 0, width: frame.width, height: frame.height))
    }
}

extension ScrollingTextView {
    override open func draw(_ dirtyRect: NSRect) {
        if point.x + stringSize.width < 0 {
            point.x += stringSize.width + spacing
        }

        textFontAttributes[NSAttributedString.Key.foregroundColor] = textColor
        text?.draw(at: point, withAttributes: textFontAttributes)

        if point.x < 0 {
            var otherPoint = point
            otherPoint.x += stringSize.width + spacing
            text?.draw(at: otherPoint, withAttributes: textFontAttributes)
        }
    }

    override open func layout() {
        super.layout()
        point.y = (frame.height - stringSize.height) / 2
    }
}

参考要点:

For anyone looking for this in Swift 4, I have converted Dave's answer and added some more functionality.

import Cocoa

open class ScrollingTextView: NSView {
    /// Text to scroll
    open var text: NSString?

    /// Font for scrolling text
    open var font: NSFont?

    /// Scrolling text color
    open var textColor: NSColor = .headerTextColor

    /// Determines if the text should be delayed before starting scroll
    open var isDelayed: Bool = true

    /// Spacing between the tail and head of the scrolling text
    open var spacing: CGFloat = 20

    /// Amount of time the text is delayed before scrolling
    open var delay: TimeInterval = 2 {
        didSet {
            updateTraits()
        }
    }

    /// Length of the scrolling text view
    open var length: CGFloat = 0 {
        didSet {
            updateTraits()
        }
    }

    /// Speed at which the text scrolls. This number is divided by 100.
    open var speed: Double = 4 {
        didSet {
            updateTraits()
        }
    }

    private var timer: Timer?
    private var point = NSPoint(x: 0, y: 0)
    private var timeInterval: TimeInterval?

    private(set) var stringSize = NSSize(width: 0, height: 0) {
        didSet {
            point.x = 0
        }
    }

    private var timerSpeed: Double? {
        return speed / 100
    }

    private lazy var textFontAttributes: [NSAttributedString.Key: Any] = {
        return [NSAttributedString.Key.font: font ?? NSFont.systemFont(ofSize: 14)]
    }()

    /**
     Sets up the scrolling text view
     - Parameters:
     - string: The string that will be used as the text in the view
     */
    open func setup(string: String) {
        text = string as NSString
        stringSize = text?.size(withAttributes: textFontAttributes) ?? NSSize(width: 0, height: 0)
        setNeedsDisplay(NSRect(x: 0, y: 0, width: frame.width, height: frame.height))
        updateTraits()
    }
}

private extension ScrollingTextView {
    func setSpeed(newInterval: TimeInterval) {
        clearTimer()
        timeInterval = newInterval

        guard let timeInterval = timeInterval else { return }
        if timer == nil, timeInterval > 0.0, text != nil {
            if #available(OSX 10.12, *) {
                timer = Timer.scheduledTimer(timeInterval: newInterval, target: self, selector: #selector(update(_:)), userInfo: nil, repeats: true)

                guard let timer = timer else { return }
                RunLoop.main.add(timer, forMode: .commonModes)
            } else {
                // Fallback on earlier versions
            }
        } else {
            clearTimer()
            point.x = 0
        }
    }

    func updateTraits() {
        clearTimer()

        if stringSize.width > length {
            guard let speed = timerSpeed else { return }
            if #available(OSX 10.12, *), isDelayed {
                timer = Timer.scheduledTimer(withTimeInterval: delay, repeats: false, block: { [weak self] timer in
                    self?.setSpeed(newInterval: speed)
                })
            } else {
                setSpeed(newInterval: speed)
            }
        } else {
            setSpeed(newInterval: 0.0)
        }
    }

    func clearTimer() {
        timer?.invalidate()
        timer = nil
    }

    @objc
    func update(_ sender: Timer) {
        point.x = point.x - 1
        setNeedsDisplay(NSRect(x: 0, y: 0, width: frame.width, height: frame.height))
    }
}

extension ScrollingTextView {
    override open func draw(_ dirtyRect: NSRect) {
        if point.x + stringSize.width < 0 {
            point.x += stringSize.width + spacing
        }

        textFontAttributes[NSAttributedString.Key.foregroundColor] = textColor
        text?.draw(at: point, withAttributes: textFontAttributes)

        if point.x < 0 {
            var otherPoint = point
            otherPoint.x += stringSize.width + spacing
            text?.draw(at: otherPoint, withAttributes: textFontAttributes)
        }
    }

    override open func layout() {
        super.layout()
        point.y = (frame.height - stringSize.height) / 2
    }
}

Reference gist:
https://gist.github.com/NicholasBellucci/b5e9d31c47f335c36aa043f5f39eedb2

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文