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!