Swift 2中Sprite-Kit多行标签怎么实现?

16

我需要在Swift 2 Sprite-Kit游戏中创建一个多行标签,文本需要环绕而不是超出屏幕。以下是我目前的代码,但我不知道该怎么做。

import Foundation
import UIKit
import SpriteKit

class JDQuotes: SKLabelNode {

    var number = 0

    init(num: Int) {
        super.init()

        if num == 1 { 

            text = "\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. laborum.\""

        }
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

我会使用一个UIlabel并将其作为子视图添加到SKView之上。 - some_id
这里有一个第三方解决方案。SKLabel通常不支持多行。https://craiggrummitt.wordpress.com/2015/04/10/multi-line-sklabels-in-swift/ - Akaino
2个回答

21

从iOS 11开始,使用SKLabelNode终于成为可能:

    let lb = SKLabelNode(fontNamed: "Copperplate")
    lb.text = "Put your long text here..."
    lb.numberOfLines = 0
    lb.preferredMaxLayoutWidth = 1000

请注意,像 numberOfLinespreferredMaxLayoutWidth 这样的新方法仅适用于 iOS 11.0 或更高版本。因此,如果您想支持旧版本,则必须放弃使用这些方法。 - peacetype

5

这里有一个Github解决方案在这里,因为SKLabelNodes不支持多行。

引用的代码:

import SpriteKit

class SKMultilineLabel: SKNode {
//props
var labelWidth:Int {didSet {update()}}
var labelHeight:Int = 0
var text:String {didSet {update()}}
var fontName:String {didSet {update()}}
var fontSize:CGFloat {didSet {update()}}
var pos:CGPoint {didSet {update()}}
var fontColor:UIColor {didSet {update()}}
var leading:Int {didSet {update()}}
var alignment:SKLabelHorizontalAlignmentMode {didSet {update()}}
var dontUpdate = false
var shouldShowBorder:Bool = false {didSet {update()}}
//display objects
var rect:SKShapeNode?
var labels:[SKLabelNode] = []

init(text:String, labelWidth:Int, pos:CGPoint, fontName:String="ChalkboardSE-Regular",fontSize:CGFloat=10,fontColor:UIColor=UIColor.blackColor(),leading:Int=10, alignment:SKLabelHorizontalAlignmentMode = .Center, shouldShowBorder:Bool = false)
{
    self.text = text
    self.labelWidth = labelWidth
    self.pos = pos
    self.fontName = fontName
    self.fontSize = fontSize
    self.fontColor = fontColor
    self.leading = leading
    self.shouldShowBorder = shouldShowBorder
    self.alignment = alignment

    super.init()

    self.update()
}

//if you want to change properties without updating the text field,
//  set dontUpdate to false and call the update method manually.
func update() {
    if (dontUpdate) {return}
    if (labels.count>0) {
        for label in labels {
            label.removeFromParent()
        }
        labels = []
    }
    let separators = NSCharacterSet.whitespaceAndNewlineCharacterSet()
    let words = text.componentsSeparatedByCharactersInSet(separators)

    let len = countElements(text)

    var finalLine = false
    var wordCount = -1
    var lineCount = 0
    while (!finalLine) {
        lineCount++
        var lineLength = CGFloat(0)
        var lineString = ""
        var lineStringBeforeAddingWord = ""

        // creation of the SKLabelNode itself
        var label = SKLabelNode(fontNamed: fontName)
        // name each label node so you can animate it if u wish
        label.name = "line\(lineCount)"
        label.horizontalAlignmentMode = alignment
        label.fontSize = fontSize
        label.fontColor = UIColor.whiteColor()

        while lineLength < CGFloat(labelWidth)
        {
            wordCount++
            if wordCount > words.count-1
            {
                //label.text = "\(lineString) \(words[wordCount])"
                finalLine = true
                break
            }
            else
            {
                lineStringBeforeAddingWord = lineString
                lineString = "\(lineString) \(words[wordCount])"
                label.text = lineString
                lineLength = label.width
            }
        }
        if lineLength > 0 {
            wordCount--
            if (!finalLine) {
                lineString = lineStringBeforeAddingWord
            }
            label.text = lineString
            var linePos = pos
            if (alignment == .Left) {
                linePos.x -= CGFloat(labelWidth / 2)
            } else if (alignment == .Right) {
                linePos.x += CGFloat(labelWidth / 2)
            }
            linePos.y += CGFloat(-leading * lineCount)
            label.position = CGPointMake( linePos.x , linePos.y )
            self.addChild(label)
            labels.append(label)
            //println("was \(lineLength), now \(label.width)")
        }

    }
    labelHeight = lineCount * leading
    showBorder()
}
func showBorder() {
    if (!shouldShowBorder) {return}
    if let rect = self.rect {
        self.removeChildrenInArray([rect])
    }
    self.rect = SKShapeNode(rectOfSize: CGSize(width: labelWidth, height: labelHeight))
    if let rect = self.rect {
        rect.strokeColor = UIColor.whiteColor()
        rect.lineWidth = 1
        rect.position = CGPoint(x: pos.x, y: pos.y - (CGFloat(labelHeight) / 2.0))
        self.addChild(rect)
    }

}
}

编辑: 你也可以检查这个版本,因为它已经更新到Swift2


1
这种方法的一个问题是SKLabelNode不像SKSpriteNode一样批量渲染。因此,如果您有20行标签,它将需要20个绘制通道,这可能会影响性能。您可以尝试将多个标签实例添加到SKEffectNode中,然后对其进行光栅化。这将减少多行标签渲染所需的绘制通道数为1个。 - Whirlwind
到目前为止,我还没有注意到它有极端的性能问题。不过我也没有过度使用多行标签,所以你可能是对的。 - Akaino
1
我很感激你的努力,别误会,但你无法处理某些情况。传递一个比“labelWidth”更长的字符串将导致无限循环(在说话时)。 - Whirlwind
2
我的Gist解决方案(在上面的答案中引用)现已更新为Swift 3。https://gist.github.com/craiggrummitt/03bfa93c07e247ee9358 - Craig Grummitt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接