16

I need to be able to make a multi-line label in swift 2 sprite-kit for a game. The text needs to wrap around rather than go off of the screen. Bellow is what I have but I do not know what to do

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")
    }
}
Beau Nouvelle
  • 6,962
  • 3
  • 39
  • 54
JaredD101
  • 161
  • 1
  • 3
  • i would just us a UIlabel and add as a subview, above the SKView. – some_id Feb 01 '16 at 00:54
  • Here is a third party solution. SKLabels generally don't support multiple lines. `https://craiggrummitt.wordpress.com/2015/04/10/multi-line-sklabels-in-swift/` – Akaino Feb 01 '16 at 12:57

2 Answers2

21

Starting iOS 11, this finally becomes possible with SKLabelNode:

    let lb = SKLabelNode(fontNamed: "Copperplate")
    lb.text = "Put your long text here..."
    lb.numberOfLines = 0
    lb.preferredMaxLayoutWidth = 1000
Mike Keskinov
  • 11,614
  • 6
  • 59
  • 87
  • Note that the newer methods like `numberOfLines` and `preferredMaxLayoutWidth` are only available on iOS 11.0 or newer. So if you want to support older versions, you will have to do without these methods. – peacetype Jun 02 '19 at 17:05
5

There is a Github solution over here as SKLabelNodes do not support multiple lines.

Code quoted:

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

}
}

EDIT: You might as well check this version too as it's updated for Swift2

Akaino
  • 1,025
  • 6
  • 23
  • 1
    One problem with this is that SKLabelNode is not rendered in batches like SKSpriteNode. So, if you have 20 lines label, it'll require 20 draw passes and this may affect on performance. You could try to add an multi-label instance to an SKEffectNode and then rasterize it. This will reduce draw passes required for multi-lined label rendering to 1. – Whirlwind Mar 19 '16 at 12:25
  • I haven't noticed extreme performance issues with it so far. I'm not overusing multiline labels though so you might be right. – Akaino Mar 19 '16 at 21:46
  • 1
    I appreciate your effort, don't get me wrong, but you don't handle certain situations. Passing a string longer than `labelWidth` will end up in infinite loop (at the moment of speaking). – Whirlwind Mar 25 '16 at 23:34
  • 2
    My solution on Gist (quoted in the answer above) is now updated for Swift 3. https://gist.github.com/craiggrummitt/03bfa93c07e247ee9358 – Craig Grummitt Oct 10 '16 at 16:00