Search code examples
iosnsattributedstringscenekit

NSAttributedString doesn't work with SCNText


I want to display some text inside a SceneKit scene, where the characters have a different color. The documentation of SCNText states:

When you create a text geometry from an attributed string, SceneKit styles the text according to the attributes in the string, and the properties of the SCNText object determine the default style for portions of the string that have no style attributes.

I tried this but it doesn't work. SCNText seems to ignore the attributes of my string. I checked if the content of my NSAttributedString is correct and added it to an UILabel, where it displays as intended.

Why is the 3D text not colored?

Here is a small test application (from the Single View Application template) and a screenshot:

Screenshot of test application

#import "ViewController.h"

#import <SceneKit/SceneKit.h>

@interface ViewController ()

@end

@implementation ViewController

- (void) viewDidLoad
{
    [super viewDidLoad];

    NSAttributedString* str = [ViewController makeSomeText];

    [self addSceneViewWithText: str];
    [self addLabelWithText: str];
}

+ (NSMutableAttributedString*) makeSomeText
{
    NSDictionary* redAttr   = @{NSForegroundColorAttributeName : [UIColor redColor]};
    NSDictionary* greenAttr = @{NSForegroundColorAttributeName : [UIColor greenColor]};
    NSDictionary* blueAttr  = @{NSForegroundColorAttributeName : [UIColor blueColor]};

    NSAttributedString* redStr   = [[NSAttributedString alloc] initWithString: @"red"   attributes: redAttr];
    NSAttributedString* greenStr = [[NSAttributedString alloc] initWithString: @"green" attributes: greenAttr];
    NSAttributedString* blueStr  = [[NSAttributedString alloc] initWithString: @"blue"  attributes: blueAttr];

    NSMutableAttributedString* str = [[NSMutableAttributedString alloc] init];
    [str appendAttributedString: redStr];
    [str appendAttributedString: greenStr];
    [str appendAttributedString: blueStr];

    return str;
}

- (void) addSceneViewWithText: (NSAttributedString*) string
{
    SCNView* view = [[SCNView alloc] initWithFrame: self.view.bounds];
    view.scene = [SCNScene scene];
    view.backgroundColor = [UIColor grayColor];
    view.autoenablesDefaultLighting = true;
    view.allowsCameraControl = true;
    SCNNode* root = view.scene.rootNode;
    [self.view addSubview: view];

    SCNNode* cameraNode = [SCNNode node];
    cameraNode.camera = [SCNCamera camera];
    cameraNode.camera.automaticallyAdjustsZRange = true;
    [root addChildNode: cameraNode];

    SCNText* text = [SCNText textWithString: string extrusionDepth: 3];
    // text.firstMaterial.diffuse.contents = [UIColor yellowColor];
    SCNNode* textNode = [SCNNode nodeWithGeometry: text];
    textNode.rotation = SCNVector4Make (1, 0, 0, 0.5f);
    [root addChildNode: textNode];

    SCNVector3 text_center;
    float dummy;
    [text getBoundingSphereCenter: &text_center radius: &dummy];
    textNode.position = SCNVector3Make (-text_center.x, -text_center.y, -150);
}

- (void) addLabelWithText: (NSAttributedString*) string
{
    CGRect bounds = self.view.bounds;
    UILabel* label = [[UILabel alloc] initWithFrame: CGRectMake (0, 50, bounds.size.width, 50)];
    label.attributedText = string;
    label.textAlignment = NSTextAlignmentCenter;
    [self.view addSubview: label];
}

- (void) didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
}

@end

Solution

  • In direct answer to your question: "Why is the 3D text not colored?"

    Colour is probably not considered part of the "style" as they're using the word in the docs. It's probably also the wrong word to be using, since it's a description of qualities of text that gets bent and twisted in every place it's ever used.

    It's likely what they mean to say is things like weight, alignment, underlining, strikethroughs, kerning and leading... things of that nature are interpreted by SceneKit. But only trial and error will find for sure what does and does not work.

    Apple should be providing a table with a list of attributes/qualities of text recognised and used by SceneKit.

    The only use of "style" I can find in the Core Text and NSAttributedString docs suggest those frameworks consider the word to group these sorts of qualities per paragraph, which is also little to no help when thinking about SceneKit text.


    Edit: Additional stuff about 3D and Materials, just in case this is news to you:

    'Materials' in 3D speak create the impressions of colour, response to light, reflectiveness and other qualities of a material on an object in 3D, and need to be created and applied. It's not nearly as simple as saying "make objectABC be red" as it is in 2D thinking.

    A single text object (in 3D speak, not how you think of these things in Cocoa) probably wouldn't have a way to automagically be made with different materials and/or Material IDs within SceneKit. Material IDs are how different materials are applied to different portions of a single object in things like 3ds Max where most people create geometry and materials.

    Unfortunately SceneKit doesn't follow this convention, instead using 'elements' as a part of geometry objects, each of which is part of an index that corresponds to whatever materials your providing that geometry.

    You can read more about this here:

    https://developer.apple.com/library/ios/documentation/SceneKit/Reference/SCNGeometry_Class/index.html#//apple_ref/doc/uid/TP40012000-CLSCHSCNGeometry-SW15

    So to get what you're looking for you're probably going to need to make 3 text objects and give each a unique material of the colour you're wanting, or break the 3 word object up into 3 elements and then have fun guessing how to get the right material onto each of them.

    EDIT: Ignore last part of above paragraph. You need to make 3 text objects to get the look you're after. The elements feature of material distribution in Scene Kit is reserved on Text Objects, so you can't break up the text at the word level into differing Elements.

    From docs:

    A text geometry may contain one, three, or five geometry elements:

    • If its extrusionDepth property is 0.0, the text geometry has one element corresponding to its one visible side.

    • If its extrusion depth is greater than zero and its chamferRadius property is 0.0, the text geometry has three elements, corresponding to its front, back, and extruded sides.

    • If both extrusion depth and chamfer radius are greater than zero, the text geometry has five elements, corresponding to its front, back, extruded sides, front chamfer, and back chamfer.