在SpriteKit中使用SKLabelNode插入换行符

时间:2013-10-04 10:27:18

标签: objective-c ios7 sprite-kit

关于如何在SpriteKit中使用SKLabelNode类插入换行符的简单问题。我有以下代码,但它不起作用 -

 SKLabelNode *nerdText = [SKLabelNode labelNodeWithFontNamed:@"Times"];
    NSString *st1 = @"Test break";
    NSString *st2 = @"I want it to break";
    NSString *test = [NSString stringWithFormat:@"%@,\r%@",st1,st2]; //Even tried \n
    nerdText.text = test;
    nerdText.fontSize = 11;
    nerdText.fontColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];
    nerdText.position = CGPointMake(150.0, 250.0);
    [self addChild:nerdText];

请帮帮我!

17 个答案:

答案 0 :(得分:16)

我认为你不能,这是一种“黑客”方式来做到这一点

SKNode *nerdText = [SKNode node]; 
SKLabelNode *a = [SKLabelNode labelNodeWithFontNamed:@"Arial"];
a.fontSize = 16;
a.fontColor = [SKColor yellowColor];
SKLabelNode *b = [SKLabelNode labelNodeWithFontNamed:@"Arial"];
b.fontSize = 16;
b.fontColor = [SKColor yellowColor];
NSString *st1 = @"Line 1";
NSString *st2 = @"Line 2";
b.position = CGPointMake(b.position.x, b.position.y - 20);
a.text = st1;
b.text = st2;
[nerdText addChild:a];
[nerdText addChild:b];
nerdText.position = CGPointMake(150.0, 250.0);
[self addChild:nerdText];

答案 1 :(得分:14)

我遇到了同样的问题。我为SKLabelNode创建了一个名为DSMultilineLabelNode的替代品,它支持自动换行,换行等。底层实现将字符串绘制到图形上下文中,然后将其应用于SKSpriteNode上的纹理。

可在GitHub上找到:

https://github.com/downrightsimple/DSMultilineLabelNode

答案 2 :(得分:9)

 static func multipleLineText(labelInPut: SKLabelNode) -> SKLabelNode {
        let subStrings:[String] = labelInPut.text!.componentsSeparatedByString("\n")
        var labelOutPut = SKLabelNode()
        var subStringNumber:Int = 0
        for subString in subStrings {
            let labelTemp = SKLabelNode(fontNamed: labelInPut.fontName)
            labelTemp.text = subString
            labelTemp.fontColor = labelInPut.fontColor
            labelTemp.fontSize = labelInPut.fontSize
            labelTemp.position = labelInPut.position
            labelTemp.horizontalAlignmentMode = labelInPut.horizontalAlignmentMode
            labelTemp.verticalAlignmentMode = labelInPut.verticalAlignmentMode
            let y:CGFloat = CGFloat(subStringNumber) * labelInPut.fontSize
            print("y is \(y)")
            if subStringNumber == 0 {
                labelOutPut = labelTemp
                subStringNumber++
            } else {
                labelTemp.position = CGPoint(x: 0, y: -y)
                labelOutPut.addChild(labelTemp)
                subStringNumber++
            }
        }
        return labelOutPut
    }

答案 3 :(得分:7)

这是你真正的另外五分钟黑客攻击。这不是太糟糕。

+(SKSpriteNode*)spritenodecontaininglabelsFromStringcontainingnewlines:(NSString*)text fontname:(NSString*)fontname fontcolor:(NSColor*)colorFont fontsize:(const CGFloat)SIZEFONT verticalMargin:(const CGFloat)VERTICALMARGIN emptylineheight:(const CGFloat)EMPTYLINEHEIGHT {
    NSArray* strings = [text componentsSeparatedByString:@"\n"];
    //DLog(@"string count: %lu", (unsigned long)strings.count);

    NSColor* color = NSColor.clearColor;
#ifdef DEBUG
    color = [NSColor colorWithCalibratedRed:1 green:0 blue:0 alpha:0.5];
#endif
    SKSpriteNode* spritenode = [SKSpriteNode spriteNodeWithColor:color size:CGSizeMake(0, 0)];

    CGFloat totalheight = 0;
    CGFloat maxwidth = 0;

    NSMutableArray* labels = [NSMutableArray array];
    for (NSUInteger i = 0; i < strings.count; i++) {
        NSString* str = [strings objectAtIndex:i];
        const BOOL ISEMPTYLINE = [str isEqualToString:@""];

        if (!ISEMPTYLINE) {
            SKLabelNode* label = [SKLabelNode labelNodeWithFontNamed:fontname];
            label.text = str;
            label.fontColor = colorFont;
            label.fontSize = SIZEFONT;

            const CGSize SIZEOFLABEL = [label calculateAccumulatedFrame].size;
            if (SIZEOFLABEL.width > maxwidth)
                maxwidth = SIZEOFLABEL.width;
            totalheight += SIZEOFLABEL.height;
            [labels addObject:label];
        }
        else {
            totalheight += EMPTYLINEHEIGHT;
            [labels addObject:[NSNull null]];
        }
        if (i + 1 < strings.count)
            totalheight += VERTICALMARGIN;
    }
    spritenode.size = CGSizeMake(maxwidth, totalheight);

    //DLog(@"spritenode total size: %@", NSStringFromSize(spritenode.size));
    CGFloat y = spritenode.size.height * 0.5;
    const CGFloat X = 0;
    for (NSUInteger i = 0; i < strings.count; i++) {
        id obj = [labels objectAtIndex:i];
        if ([obj isKindOfClass:SKLabelNode.class]) {
            SKLabelNode* label = obj;
            label.verticalAlignmentMode = SKLabelVerticalAlignmentModeTop;
            label.position = ccp(X, y);
            [spritenode addChild:label];
            const CGSize SIZEOFLABEL = [label calculateAccumulatedFrame].size;
            y -= SIZEOFLABEL.height;
        }
        else {
            y -= EMPTYLINEHEIGHT;
        }
        if (i + 1 < labels.count)
            y -= VERTICALMARGIN;
    }

    return spritenode;
}

顺便说一下,你需要

static inline CGPoint ccp( CGFloat x, CGFloat y )
{
    return CGPointMake(x, y);
}

答案 4 :(得分:7)

因此,在做了一些研究之后,我了解到SkLabelNode并不打算涉及多行字符串。由于功能受SKLabelNode的限制,因此只需使用 UILabel 来保存文本的位置就更有意义了。学习如何顺利地将UI元素实现到精灵工具包中,使生活变得更加轻松。 UI元素以编程方式创建,并使用

添加到场景中
[self.view addsubview:(your UIelement)];

所以你需要做的就是 1.初始化UIelement的实例,在这种情况下是UIlabel

UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(50, 50, 100, 100)];
label.backgroundColor = [UIColor whiteColor];
label.textColor = [UIColor blackColor];
label.text = @"helllllllllo";

2。创建UIelement后,只需将其添加到到视图,使用上述方法

3.我发现重要的是要注意UI元素和SK元素在定位时不会相同。提供了一些简单的方法,例如convertPointToView

-(CGPoint)convertPointToView(CGPoint);

在转换积分方面提供帮助。我希望这有助于祝你好运!

答案 5 :(得分:6)

我为Swift 3编写了一个解决方案。

开源GitHub项目提供了一个Xcode演示项目:https://github.com/benmorrow/Multilined-SKLabelNode

以下是SKLabelNode扩展程序:

extension SKLabelNode {
  func multilined() -> SKLabelNode {
    let substrings: [String] = self.text!.components(separatedBy: "\n")
    return substrings.enumerated().reduce(SKLabelNode()) {
      let label = SKLabelNode(fontNamed: self.fontName)
      label.text = $1.element
      label.fontColor = self.fontColor
      label.fontSize = self.fontSize
      label.position = self.position
      label.horizontalAlignmentMode = self.horizontalAlignmentMode
      label.verticalAlignmentMode = self.verticalAlignmentMode
      let y = CGFloat($1.offset - substrings.count / 2) * self.fontSize
      label.position = CGPoint(x: 0, y: -y)
      $0.addChild(label)
      return $0
    }
  }
}

以下是您使用它的方式:

let text = "hot dogs\ncold beer\nteam jerseys"
let singleLineMessage = SKLabelNode()
singleLineMessage.fontSize = min(size.width, size.height) /
  CGFloat(text.components(separatedBy: "\n").count) // Fill the screen
singleLineMessage.verticalAlignmentMode = .center // Keep the origin in the center
singleLineMessage.text = text
let message = singleLineMessage.multilined()
message.position = CGPoint(x: frame.midX, y: frame.midY)
message.zPosition = 1001  // On top of all other nodes
addChild(message)

这是应用程序的样子:

simulator screenshot multiline SKLabelNode

答案 6 :(得分:4)

另一种方法是创建文本的位图版本,然后将结果图像与SKSpriteNode一起使用。

比听起来容易。

一个例子,假设我们有一个字符串或一个属性字符串和一个CGSize变量,其大小与结果文本区域相同。

CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();

// Assuming size is in actual pixels. Multiply size by the retina scaling 
// factor if not.
CGContextRef context = CGBitmapContextCreate(NULL, (size_t)round(size.width), (size_t)round(size.height), 8, (size_t)round(size.width) * 4, rgbColorSpace, (CGBitmapInfo)kCGImageAlphaPremultipliedLast);

CGColorSpaceRelease(rgbColorSpace);

// Draw text, potentially flipping the coordinate system before 
// (depending on methods you use).
// Make sure that you draw the font twice as big for retina.
// E.g. [@"My text" drawInRect:rect withAttributes:attr];

// Once we have drawn the text, simply extract the image and
// Make a texture from it.

CGImageRef image = CGBitmapContextCreateImage(context);
SKTexture *texture = [SKTexture textureWithCGImage:image];
CGImageRelease(image);
CGContextRelease(context);

// Texture created, so make a sprite node to use it.
SKSpriteNode *node = [self node];
node.texture = texture;

// Set the node size to the size in non-retina pixels, so if size was with
// scale factor already multiplied in, then we would need to divide by the scale
// factor. 
node.size = size;

答案 7 :(得分:4)

这里只是为了贡献我的解决方案。我发现自己想要同样的事情 - 从长字符串中创建SKLabelNode的多行。逐个创建它并手动定位它们是不实际的。所以我做了一个更简单的方法来制作多行SKLabelNode。此方法使用SKLabelNodes(而不是将文本捕获到图像中)。

如果您有兴趣,请参阅我的解决方案: http://xcodenoobies.blogspot.com/2014/12/multiline-sklabelnode-hell-yes-please-xd.html

结果:

enter image description here

答案 8 :(得分:4)

这里有很多不错的解决方案,但我没有看到任何快速写的,所以我们走了。这个函数将接受一个长字符串,并在你放置\ n字符的地方将其分解。

 func createMultiLineText(textToPrint:String, color:UIColor, fontSize:CGFloat, fontName:String, fontPosition:CGPoint, fontLineSpace:CGFloat)->SKNode{

    // create node to hold the text block
    var textBlock = SKNode()

    //create array to hold each line
    let textArr = textToPrint.componentsSeparatedByString("\n")

    // loop through each line and place it in an SKNode
    var lineNode: SKLabelNode
    for line: String in textArr {
        lineNode = SKLabelNode(fontNamed: fontName)
        lineNode.text = line
        lineNode.fontSize = fontSize
        lineNode.fontColor = color
        lineNode.fontName = fontName
        lineNode.position = CGPointMake(fontPosition.x,fontPosition.y - CGFloat(textBlock.children.count ) * fontSize + fontLineSpace)
        textBlock.addChild(lineNode)
    }

    // return the sknode with all of the text in it
    return textBlock
}

答案 9 :(得分:3)

从iOS 11 / macOS 10.13开始,SKLabelNode具有numberOfLines属性,其行为与UILabel具有相似的行为。默认情况下,它设置为1。如果将其设置为零,则行数不受限制。另请参见lineBreakModepreferredMaxLayoutWidth。我认为值得在此指出这一点,以防任何人在看到Apple文档之前到达此页面。如果您的最低构建目标是iOS 11 / macOS 10.13,则不需要上面发布的帮助程序方法。

答案 10 :(得分:2)

如果有人有兴趣,我创建了一个更好的SKLabelNode名为SKLabelNodePlus,它具有像Chris Allwein这样的多线支持,但也有其他我觉得非常有用的功能。
在GitHub上查看:

https://github.com/MKargin0/SKLabelNodePlus

答案 11 :(得分:2)

和其他几个人一样,我自己也实现了这个问题的解决方案。它是一个简单的SKLabelNode子类,可用作常规SKLabelNode的替代。我发现继承了这个功能的最佳方法,因为我在“所有”时间“无处不在”地使用它......

整个事情可以在github(对任何感兴趣的人)获得,但主要要点如下:它分隔字符串并创建常规SKLabelNode实例,并将这些广告作为节点的子节点。只要调用setText:,就会执行此操作:

- (void)setText:(NSString *)text{
    self.subNodes    = [self labelNodesFromText:text];
    [self removeAllChildren];
    for (SKLabelNode *childNode in self.subNodes) {
        [self addChild:childNode];
    }
    _text    = @""; // (synthesized in the implementation)
}

标签子节点在此处创建:

- (NSArray *)labelNodesFromText:(NSString *)text{
    NSArray *substrings    = [text componentsSeparatedByString:@"\n"];
    NSMutableArray *labelNodes    = [[NSMutableArray alloc] initWithCapacity:[substrings count]];

    NSUInteger labelNumber    = 0;
    for (NSString *substring in substrings) {
        SKLabelNode *labelNode    = [SKLabelNode labelNodeWithFontNamed:self.fontName];
        labelNode.text    = substring;
        labelNode.fontColor    = self.fontColor;
        labelNode.fontSize    = self.fontSize;
        labelNode.horizontalAlignmentMode    = self.horizontalAlignmentMode;
        labelNode.verticalAlignmentMode    = self.verticalAlignmentMode;
        CGFloat y    = self.position.y - (labelNumber * self.fontSize * kLineSpaceMultiplier); // kLineSpaceMultiplier is a float constant. 1.5 is the value I have chosen
        labelNode.position    = CGPointMake(self.position.x, y);
        labelNumber++;
        [labelNodes addObject:labelNode];
    }

    return [labelNodes copy];
}

您可能已经注意到我还有一个属性subNodes(数组)。这在其他地方派上用场,因为完整实现还允许使用常规SKLabelNode语法更改任何属性。 (Text,fontName,fontSize,alignment等)

答案 12 :(得分:1)

使用https://github.com/downrightsimple/DSMultilineLabelNodeHow to write text on image in Objective-C (iOS)?作为参考,这是我为快速而又脏的方式获取文本包装SKNode(Xcode 7.1.1)所做的:

-(SKNode*)getWrappingTextNode:(NSString*)text maxWidth:(CGFloat)width {
    UIImage *img = [self drawText:text widthDimension:width];
    return [SKSpriteNode spriteNodeWithTexture:[SKTexture textureWithImage:img]];
}

-(UIImage*)drawText:(NSString*)text widthDimension:(CGFloat)width {
    NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
    paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
    paragraphStyle.alignment = NSTextAlignmentLeft; //or whatever alignment you want

    UIFont *font = [UIFont fontWithName:@"Verdana" size:22]; //or whatever font you want

    NSDictionary *att = @{NSFontAttributeName:font, NSParagraphStyleAttributeName: paragraphStyle};

    //using 800 here but make sure this height is greater than the potential height of the text (unless you want a max-height I guess but I did not test max-height)
    CGRect rect =  [text boundingRectWithSize:CGSizeMake(width, 800) options:NSStringDrawingUsesLineFragmentOrigin attributes:att context:nil];

    UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.0f);

    [text drawInRect:rect withAttributes:att];

    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return newImage;
}

答案 13 :(得分:0)

我编写了一个实用程序方法来获取一个字符串并将其分成一个给定最大长度的字符串数组。它会自动以整个单词结束每一行并删除前导空格。希望它能帮助别人!

- (NSArray*)linesFromString:(NSString*)string withMaxLineLength:(int)maxLineLength;
{
    NSMutableArray *lines = [NSMutableArray arrayWithCapacity:1];

    BOOL gotLine = NO;
    BOOL doneFormat = NO;
    BOOL endOfString = NO;
    int innerLoops = 0;
    int outerLoops = 0;
    int lineIndex = 0;
    int currentStringIndex = 0;
    int stringLength = (int)[string length];
    int rangeLength = maxLineLength;
    NSString *line;
    NSString *testChar;
    NSString *testChar2;

    while (!doneFormat) {
        outerLoops++;
        while (!gotLine) {
            endOfString = NO;
            innerLoops++;
            line = [string substringWithRange:NSMakeRange(currentStringIndex, rangeLength)];

            testChar = [line substringWithRange:NSMakeRange(0, 1)];
            if (currentStringIndex + rangeLength > [string length] - 1) {
                endOfString = YES;
            } else {
                testChar2 = [string substringWithRange:NSMakeRange(currentStringIndex + rangeLength, 1)];
            }

            //If the line starts with a space then advance 1 char and try again.
            if ([testChar isEqualToString:@" "]) {
                currentStringIndex++;
                // If we were at the end of the string then reduce the rangeLength as well.
                if (endOfString) {
                    rangeLength--;
                }

            // else, if this line ends at the end of a word (or the string) then it's good. ie next char in the string is a space.
            } else if ([testChar2 isEqualToString:@" "] || endOfString) {

                gotLine = YES;
                currentStringIndex += [line length];

            // else, make the line shorter by one character and try again
            } else if (rangeLength > 1){
                rangeLength--;

                // Otherwise the word takes up more than 1 line so use it all.
            } else {
                line = [string substringWithRange:NSMakeRange(currentStringIndex, maxLineLength)];
                currentStringIndex += [line length];
                gotLine = YES;
            }

            // Make sure we're not stuck in an endless loop
            if (innerLoops > 1000) {
                NSLog(@"Error: looped too long");
                break;
            }
        }

        // If we processed a line, and the line is not nil, add it to our array.
        if (gotLine && line) {
            [lines insertObject:line atIndex:lineIndex];
            lineIndex++;
        }

        // Reset variables
        rangeLength = maxLineLength;
        gotLine = NO;

        // If the current index is at the end of the string, then we're done.
        if (currentStringIndex >= stringLength) {
            doneFormat = YES;

        // If we have less than a full line left, then reduce the rangeLength to avoid throwing an exception
        } else if (stringLength - (currentStringIndex + rangeLength) < 0) {
            rangeLength = stringLength - currentStringIndex;
        }

        // Make sure we're not stuck in an endless loop
        if (outerLoops > 1000) {
            NSLog(@"Error: Outer-looped too long");
            break;
        }
    }
    return lines;
}

然后我只是调用它并创建一些标签节点以添加到我的图层节点,如下所示。我在下方和button2的左边缘对齐我的线条标签,因此所有线条左对齐。

CGFloat fontSize = 30.0f;
int lineCount;
NSString *description = [product localizedDescription];
NSString *line;
NSArray *lines = [self linesFromString:description withMaxLineLength:43];

if (lines) {

    lineCount = (int)[lines count];

    for (int i = 0; i < lineCount; i++) {

        line = [lines objectAtIndex:i];

        // Create a new label for each line and add it to my SKSpriteNode layer
        SKLabelNode *label = [SKLabelNode labelNodeWithFontNamed:@"Superclarendon-Black"];
        label.text = line;
        label.fontSize = fontSize;
        label.scale = 1.0f;
        label.name = @"lineLabel";
        label.fontColor = [UIColor blackColor];
        label.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeLeft;
        label.position = CGPointMake(button2.position.x - button2.size.width * 0.5f, button2.position.y - button2.size.height - i * fontSize * 1.1);
        [layer addChild:label];
    }
}

答案 14 :(得分:0)

这是一个快速简单的功能,我写的只是为了让生活更轻松。

步骤1)传入字符串,获取SKSpriteNode。

步骤2)将精灵节点添加到场景中。

    /******************************************************************************/
- (SKSpriteNode*) ConvertString: (NSString*) str
                   WithFontSize: (NSInteger) font_size
            ToParagraphWithSize: (CGSize) para_size
{
   SKSpriteNode* paragraph = [[SKSpriteNode alloc] initWithColor: [SKColor clearColor]
                                                            size: para_size];

   // Set the anchor point to the top left corner. This is where English
   // paragraphs usually start
   paragraph.anchorPoint = CGPointMake(0,1);

   // Create an array to hold multilple sub strings.  These sub strings will
   // become multiple SKLabels that will be added to the paragraph sprite node
   // created above
   NSMutableArray* str_arr = [[NSMutableArray alloc] init];

   // Lets separate words by a single space.
   NSArray* word_arr = [str componentsSeparatedByString:@" "];

   // 50% is an approximate character height to width ratio.  Change this
   // number to adjust the number of characters per line you would like.
   // Increase it if you have a lot of capitol W's
   float est_char_width = font_size * 0.50;
   NSInteger num_char_per_line = para_size.width / est_char_width;

   // For every word in the original string, make sure it fits on the line
   // then add it to the string array.
   NSString* temp_str = @"";
   for (NSString* word in word_arr)
   {
      if ((NSInteger)word.length <= num_char_per_line - (NSInteger)temp_str.length)
      {
         temp_str = [NSString stringWithFormat:@"%@ %@", temp_str, word];
      }
      else
      {
         [str_arr addObject: temp_str];
         temp_str = word;
      }
   }
   [str_arr addObject: temp_str];

   // For every sub string, create a label node and add it to the paragraph
   for (int i = 0; i < str_arr.count; i++)
   {
      NSString* sub_str = [str_arr objectAtIndex: i];
      SKLabelNode* label = [self CreateLabelWithText: sub_str];
      label.fontSize = 14;
      label.position = CGPointMake(0, -(i+1) * font_size);
      [paragraph addChild: label];
   }

   return paragraph;
}


/******************************************************************************/
- (SKLabelNode*) CreateLabelWithText: (NSString*) str
{
   enum alignment
   {
      CENTER,
      LEFT,
      RIGHT
   };

   SKLabelNode* label;
   label = [SKLabelNode labelNodeWithFontNamed:@"ChalkboardSE-Light"];
   label.name = @"label_name";
   label.text = str;
   label.zPosition = 1;
   label.horizontalAlignmentMode = LEFT;
   label.fontColor = [SKColor whiteColor];

   return label;
}

答案 15 :(得分:0)

所以我知道这个问题有点老了,但是以防万一像我一样回到现在,现在有一个属性preferredMaxLayoutWidth可以与lineBreakMode和{{1 }}:

示例:

numberOfLines

答案 16 :(得分:0)

在场景编辑器中,将SKLabelNode的文本更改为右窗格中属性检查器中的属性,如下所示。

inspector

这样做将使您有很大的自由度来自定义显示的文本,而不必创建多个SKLabelNode实例或UIImage。例如,您可以创建一个如下所示的段落。

enter image description here

对于编程交互,请使用标签节点的attributedString属性添加自定义属性。