Feedback Requested: Binding NSAttributedString

As NSAttributedString plays a big role, I have been trying to figure out a way of improving the process by which NSAttributedString are created from C#.

Status Today

While we support the NSDictionary-based approach of creating the attributed needed for an attributed string, like this:

var attrs = new NSMutableDictionary () {
  { NSAttributedString.FontAttributeName,
    UIFont.FromName ("Heletica 14") },

  { NSAttributedString.ForegroundColorAttributeName,
    UIColor.Black }
};

var myString = new NSAttributedString ("Hello", attrs);

If you ignore the fact that Helvetica 14 is an uninspiring and inconsequential font, the example above is error prone.

Developers can pass a UIColor where a UIFont was required, or a number or anything else. They also have no idea what values are acceptable unless they take a trip to the documentation and find out which values are allowed, and the types of their values.

The Standard Choice

What we have historicallly in situations like this is to create a helper, strongly typed class. This allows the IDE to provide intellisense for this situation, explicitly listing the types allowed and ensuring that only the correct values are set. If we use this approach, we would introduce a new class, let us say "NSStringAttributes":

var attrs = new NSStringAttributes () {
  Font = UIFont.FromName ("Heletica 14"),
  ForegroundColor = UIColor.Black
};

var myString = new NSAttributedString ("Hello", attrs);

The only problem that I have with this approach is that now we have two classes: NSAttributedString which is the actual string with the given attribute and a class that has a name that resembles too much NSAttributedString.

My concern is not that seasoned developers would be confused between NSAttributedString and NSStringAttributes, but that developers new to the platform would rightfully ask why they need to know the differences about this.

The upside is that it follows the existing pattern in MonoTouch and MonoMac: use a strongly typed class, which internally produces the NSDictionary on demand.

Giving NSAttributedString special powers

Another option is to give NSAttributedString special powers. This would allow NSAttributedString instances to be configured like this:

var myString = new NSAttributedString ("Hello") {
  Font = UIFont.FromName ("Helvetica 14"),
  ForegroundColor = UIColor.Black
}

To support the above configuration, we would have to delay the actual creation of the NSAttributedString from the constructor time until the object is actually used.

This would allow the user to set the Font, ForegroundColor and other properties up until the point of creation. This would require the NSAttributedString type to be treated specially by the MonoTouch/MonoMac bindings.

It would also make the NSMutableAttributedString feel a bit better to use: users could make changes to the attributed string all day long, and apply changes to the various properties for the entire span of the text with a simple property value:

var myString = new NSMutableAttributedString ("Hello");

// This:
myString.AddAttribute (
	NSAttributedString.ForegroundColorAttributeName,
	UIColor.Red,
	new NSRange (0, myString.Length));

// Would become:
myString.ForegroundColor = UIColor.Red;

There are a couple of downsides with the above approach. The actual attributes used for this string configuration would not be shared across different NSAttributedStrings, so for some code patterns, you would be better off not using this syntax and instead using the NSStringAttributes class.

The other downside is that NSAttributedString properties could be set only up to the point of the string being used. Once the string is used, the values would be set in stone, and any attempt to change them would throw an exception, or issue a strongly worded message on the console.

And of course, the improved NSMutableAttributedString API improvements could be done independently of the property setters existing in the base class.

Others?

Can anyone think of other strongly typed approaches to simplify the use of NSAttributedStrings that are not listed here?

Update

Thanks for your excellent feedback! It helped us clarify what we wanted to do with the API. We are going to go with the "Standard Choice", but with a small twist.

We came to realize that NSAttributedString is just a string with attributes, but the attributes are not set in stone. It is really up to the consumer of the API to determine what the meaning of the attributes are.

We had a constructor that took a CTStringAttributes parameter which is used when you render text with CoreText.

What we are going to do is introduce a UIStringAttributes for iOS to set UIKit attributes and an NSStringAttributes for AppKit that will have the same behavior: they will be strongly typed classes that can be passed as a parameter to the NSAttributedString constructor.

So we will have basically three convenience and type safe constructors based on what you will be using the NSAttributedString with as well as the standard NSDictionary constructor for your own use:

public class NSAttributedString : NSObject {
  public NSAttributedString (string str, NSDictionary attrs);
  public NSAttributedString (string str, CTStringAttributes attrs);

  // iOS only
  public NSAttributedString (string str, UIStringAttributes attrs);

  // OSX only
  public NSAttributedString (string str, NSStringAttributes attrs);
}

We will also provide convenience "GetAttributes" methods for all platforms:

public class NSAttributedString : NSObject {
  public CTStringAttributes GetUIKitAttributes ();

  // Only on iOS
  public UIStringAttributes GetUIKitAttributes ();

  // Only on OSX
  public NSStringAttributes GetUIKitAttributes ();
}

Finally, we loved Mark Rendle's proposal of using default parameters and named parameters for C#. This actually opened our eyes to a whole new set of convenience constructors that we can use to improve both the MonoTouch and MonoMac APIs.

This comes from a Sample I was working on:

var text = new NSAttributedString (
    "Hello world",
    font: GetFont ("HoeflerText-Regular", 24.0f),
    foregroundColor: GetRandomColor (),
    backgroundColor: GetRandomColor (),
    ligatures: NSLigatureType.All, 
    kerning: 10, // Very classy!
    underlineStyle: NSUnderlineStyle.Single,
    shadow: new NSShadow () {
        ShadowColor = GetRandomColor (),
        ShadowOffset = new System.Drawing.SizeF (2, 2)
    },
    strokeWidth: 5);
#endif

The only open question is whether the parameter names in this case should be camelCase, or start with an uppercase letter (font vs Font and foregroundColor vs ForegroundColor).

The result on screen are beautiful!

Posted on 24 Aug 2012 by Miguel de Icaza
This is a personal web page. Things said here do not represent the position of my employer.