rightpoint / bonmot Goto Github PK
View Code? Open in Web Editor NEWBeautiful, easy attributed strings in Swift
License: MIT License
Beautiful, easy attributed strings in Swift
License: MIT License
It can be difficult to debug advanced attributed strings, because certain characters (image attachments, various kinds of white space or zero-width characters, Unicode character) are hard to decipher in -[NSAttributeString description]
’s output. BonMot should be able to output debug strings like this (features labeled for emphasis):
↓ no need to call out normal inner spaces
[24×36 px attached image][non-breaking thin space]My Super Duper String[Trailing Space]
↑ image attachments ↑ proper Unicode names of whitespace chars ↑ hard-to-see characters
It should also be able to generate an Objective-C (or Swift?) line of code that would use a format string to recreate this string, for use in unit tests or for debugging purposes.
Maybe also a definition.
New line characters are part of the line they are on, rather than the line they lead in to. Rather than having to create and append a text configuration with a \n
each time you want s new line, there should be a shorthand to append a new line to an existing BONChain
.
if ( trackingInPoints > 0.0f ) {
This line in BONTextConfiguration
precludes negative tracking values, which should be supported. This should be changed to trackingInPoints != 0.0f
or, to appease the float comparison gods, a custom floatsCloseEnough(trackingInPoints, 0.0f)
.
-[UIImage rz_tintedImageWithColor:]
exists, but you have to use it first before inserting the image into the string. It should be done automatically if a string with an embedded image also has a textColor
set (or maybe use a separate imageColor
property).
Paragraph styles are respected only when applied to the first character of a string, or to the first character after a \n
in a multi-paragraph string. Are there improvements that can be made in BonMot's handling of paragraph style attributes?
Forgot to include a section on the custom constraints.
Line height, leading, and line height multiple all affect the distance between the top and bottom of a label and its text. BONTextAlignmentConstraint
should take these into account, based on a combination of the font
and the attributedText
’s paragraph style.
They're off by default in UIKit. Add ability to enable them if supported by the font.
BonMot was started with CocoaPods 0.35 for reasons. It should be updated to use the latest CocoaPods at some point.
Right now, you chain BONTextConfiguration
objects in BONChainLink
wrappers using the RZCursive
macro. There are some parts I like, but I would prefer more descriptive names that give some hint as to what each part does. I would also prefer two names (configuration and wrapper), or even one (just configuration), rather than three (configuration, wrapper, and macro)
You should be able to save and restore BONChain
objects and have everything be intact, including image attachments.
case BONConstraintAttributeFirstBaseline: // fall through
case BONConstraintAttributeLastBaseline: // fall through
case BONConstraintAttributeBottom: // fall through
case BONConstraintAttributeUnspecified: {
[NSException raise:NSInternalInconsistencyException format:@"Not implemented yet"];
break;
}
Provide a convenience wrapper around CTFontCopyFeatures()
so users can easily see if a font supports a requested feature. Could possibly have it take a parameter, and do the heavy lifting behind the scenes:
typedef NS_ENUM(NSUInteger, BONFontFeature) {
BONFontFeatureUnknown = 0,
BONFontFeatureSmallCaps,
BONFontFeatureMonospaceFigures,
...
}
+ (BOOL)font:(UIFont *)font supportsFeature:(BONFontFeature)feature;
If the font supports native small caps, we should be able to use them. If not, a runtime error should occur - no ugly small caps here!
BonMot should have a ReadMe that illustrates why it is useful, what it can do, how to get started, and where to learn more.
I'm currently only using it for observing .font
in the custom constraint, but I want to write the KVO by hand to remove the dependency. Thanks to @ateliercw for the idea.
Some fonts have alternate glyphs for some characters. These include standard kinds like small caps and superscripts/subscripts, and also include stylistic alternates for different uses (e.g. different ®
for headline and body text). BonMot should have a way to query which of these is are available in a font, and to enable easy use of them if they are available. The querying part can be done using the C functions in Core Text.
To use characters like non-breaking thin spaces and other difficult-to-type-and-also-to-see symbols, you usually have to consult a Unicode reference and then include an obtuse Unicode code point in your string. BonMot should make it easy to add special characters to strings by providing a convenient API to access these characters. It should be easy to add these characters in the context of concatenating attributed strings. For example, you might want to make a string like this:
[image][non-breaking thin space]Some Text[thin space][image][non-breaking thin space]More Text
This typically takes multiple join/concatenate passes, with obscure Unicode characters or wordy abstractions. BonMot should make the code to generate strings like this concise and readable.
Xcode 7 beta 5 caused BONSpecialGenerator.swift to fail to compile:
scripts/BONSpecialGenerator.swift:38:51: error: expression does not conform to type 'NilLiteralConvertible'
CFStringTransform(theCFMutableString, nil, kCFStringTransformToUnicodeName, 0)
^~~
scripts/BONSpecialGenerator.swift:63:36: error: 'split(_:maxSplit:allowEmptySlices:isSeparator:)' is unavailable: Use the split() method instead.
let components: [String] = split(self.characters){$0 == " " || $0 == "-"}.map(String.init)
^~~~~
Swift.split:1:76: note: 'split(_:maxSplit:allowEmptySlices:isSeparator:)' has been explicitly marked unavailable here
@available(*, unavailable, message="Use the split() method instead.") func split<S : CollectionType, R : BooleanType>(elements: S, maxSplit: Int = default, allowEmptySlices: Bool = default, @noescape isSeparator: (S.Generator.Element) -> R) -> [S.SubSequence]
^
scripts/BONSpecialGenerator.swift:79:27: error: 'stringByDeletingLastPathComponent' is unavailable: Use stringByDeletingLastPathComponent on NSString instead.
let path = script.stringByDeletingLastPathComponent
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Foundation.String:299:7: note: 'stringByDeletingLastPathComponent' has been explicitly marked unavailable here
var stringByDeletingLastPathComponent: String { get }
^
scripts/BONSpecialGenerator.swift:87:33: error: 'stringByDeletingLastPathComponent' is unavailable: Use stringByDeletingLastPathComponent on NSString instead.
let path = path.stringByDeletingLastPathComponent
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Foundation.String:299:7: note: 'stringByDeletingLastPathComponent' has been explicitly marked unavailable here
var stringByDeletingLastPathComponent: String { get }
^
scripts/BONSpecialGenerator.swift:126:43: error: 'stringByAppendingPathComponent' is unavailable: Use stringByAppendingPathComponent on NSString instead.
let headerTemplatePath = currentDirectory.stringByAppendingPathComponent("BONSpecial.h template.txt")
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Foundation.String:287:8: note: 'stringByAppendingPathComponent' has been explicitly marked unavailable here
func stringByAppendingPathComponent(aString: String) -> String
^
scripts/BONSpecialGenerator.swift:127:51: error: 'stringByAppendingPathComponent' is unavailable: Use stringByAppendingPathComponent on NSString instead.
let implementationTemplatePath = currentDirectory.stringByAppendingPathComponent("BONSpecial.m template.txt")
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Foundation.String:287:8: note: 'stringByAppendingPathComponent' has been explicitly marked unavailable here
func stringByAppendingPathComponent(aString: String) -> String
^
scripts/BONSpecialGenerator.swift:145:41: error: 'stringByDeletingLastPathComponent' is unavailable: Use stringByDeletingLastPathComponent on NSString instead.
let projectDirectory = currentDirectory.stringByDeletingLastPathComponent
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Foundation.String:299:7: note: 'stringByDeletingLastPathComponent' has been explicitly marked unavailable here
var stringByDeletingLastPathComponent: String { get }
^
scripts/BONSpecialGenerator.swift:146:41: error: 'stringByAppendingPathComponent' is unavailable: Use stringByAppendingPathComponent on NSString instead.
let classesDirectory = projectDirectory.stringByAppendingPathComponent("Pod/Classes")
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Foundation.String:287:8: note: 'stringByAppendingPathComponent' has been explicitly marked unavailable here
func stringByAppendingPathComponent(aString: String) -> String
^
scripts/BONSpecialGenerator.swift:149:35: error: 'stringByAppendingPathExtension' is unavailable: Use stringByAppendingPathExtension on NSString instead.
let headerFileName = baseFileName.stringByAppendingPathExtension("h")!
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Foundation.String:291:8: note: 'stringByAppendingPathExtension' has been explicitly marked unavailable here
func stringByAppendingPathExtension(ext: String) -> String?
^
scripts/BONSpecialGenerator.swift:150:43: error: 'stringByAppendingPathExtension' is unavailable: Use stringByAppendingPathExtension on NSString instead.
let implementationFileName = baseFileName.stringByAppendingPathExtension("m")!
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Foundation.String:291:8: note: 'stringByAppendingPathExtension' has been explicitly marked unavailable here
func stringByAppendingPathExtension(ext: String) -> String?
^
scripts/BONSpecialGenerator.swift:151:39: error: 'stringByAppendingPathComponent' is unavailable: Use stringByAppendingPathComponent on NSString instead.
let headerFilePath = classesDirectory.stringByAppendingPathComponent(headerFileName)
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Foundation.String:287:8: note: 'stringByAppendingPathComponent' has been explicitly marked unavailable here
func stringByAppendingPathComponent(aString: String) -> String
^
scripts/BONSpecialGenerator.swift:152:47: error: 'stringByAppendingPathComponent' is unavailable: Use stringByAppendingPathComponent on NSString instead.
let implementationFilePath = classesDirectory.stringByAppendingPathComponent(implementationFileName)
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Foundation.String:287:8: note: 'stringByAppendingPathComponent' has been explicitly marked unavailable here
func stringByAppendingPathComponent(aString: String) -> String
^
BONTextAlignmentConstraint
strongly holds its firstItem
and secondItem
internally. This causes a known retain cycle if a view referenced by the constraint also strongly holds the constraint. However, there may be unintended retain cycles caused by internal details of the layout system. This should be tested to determine whether it is a problem.
You can use paragraph styles to control first-line indent and left-indent. We should be able to use these in concert to make attached images behave like list bullets. Maybe we can use tab stops, if they're supported on iOS?
BonMot should have full test coverage, both of string generation and of ancillary tasks such as custom constraints and image tinting (assuming those are not split out into external projects).
Using .nextTextConfiguration
can be cumbersome, since there is currently not DSL syntax to easily chain text configuration objects together. This should either be streamlined, or it should be removed, and you just use categories on NSArray
to join text configuration objects.
From the iOS 8.3 UILabel
documentation:
To turn on auto-kerning in the label, set
NSKernAttributeName
of the string to[NSNull null]
.
To research
UILabel
? What about UITextView
, UITextField
, Core Animation, and Core Graphics?Don't bother implementing this unless someone specifically requests it for a shipping product, and even then, try to talk them out of it. No one likes underlining except my high school English teachers.
Custom constraints and inline images are more visually compelling than some of the simpler examples, so move them higher in the sample app. Move the files in the Xcode Project Navigator to match.
Thanks for the idea, @KingOfBrian
In Apple's WWDC 2012 talk that introduces attributed strings on iOS 6, they mention that modifying an attributed string is more efficient than concatenating multiple attributed strings. I would like to write some tests to confirm this, and if it is still true, change BonMot to use mutable attributed strings when building its strings.
Currently, RZTextAlignmentConstraint
works in IB but not in code. When writing programmatic constraints, you should be able to easily create constraints that pin labels' cap height, x-height, or baseline to any vertical attribute of any other view.
The mutual exclusivity of properties like adobeTracking
and pointTracking
is enforced in BONChain
, but you can use BONText
on its own, in which case you don't get the auto-clobbering.
-debugDescription
on BONText
and BONChain
use these methods.NSString
to make this more streamlined.The wiki attached to this project should include detailed examples of what each font property and custom constraint attribute can do, complete with screenshots and usage examples.
Given this string:
The quick brown fox jumped over the lazy red dog.
If you want to make the color words be different colors using BonMot, you would have to construct the string out of the following components:
The quick[space]
brown
(colored brown)[space]fox jumped over the lazy[space]
red
(colored red)[space]dog.
The alternative is to use BonMot to generate attributes, then apply those attributes to ranges of a mutable copy of the attributed string yourself. BonMot may be able to streamline this process.
If a font has super/sub character alternates, BonMot should be able to use them. If it does not, BonMot should be able to report this in a run-time log message or exception.
Nice to have: if a font doesn't have proper super/sub alternates, some designers will still accomplish the effect using a heaver weight, smaller size, and baseline shift (all of which are currently supported in BonMot). Can we make this process more streamlined, perhaps with special API for creating super/sub attributes to use when real alternate characters are not available?
Support NSTextAlignment
on strings.
To research:
I'd like to see carthage support alongside cocoa pods support so that I can use my dependency manager of choice
I had to add this line to .travis.yml
:
osx_image: beta-xcode6.3
Once Travis adds non-beta support for Xcode 6.3, we should remove this line.
I didn't know XCTAssertEqualWithAccuracy
existed.
Attributed strings can color their text, so they should be able to tint their images as well. You can't use UIKit’s tintColor
property on UIImage
directly, so BonMot should include the ability to arbitrarily tint images.
Example project should have some more advanced examples. @jkaufman suggested something along these lines:
I would also like to include thin/hairline/nonbreaking/weird spaces and text attachments
One of the intents of BonMot is to make it easy to write unit tests for the attributed strings that a view model might return. These strings may contain hard-to-distinguish characters such as nonbreaking spaces, which can make it difficult to write unit tests.
BonMot should have a method to transform a string into a human-readable version, like this:
{image attachment}{nonbreaking space}My cool label text{hair space}{image attachment}{nonbreaking space}Another piece of label text
The purpose would be to write a unit testing macro that works like this:
BONAssertEquivalentStrings(someViewModel.someProperty, @"{image attachment}{nonbreaking space}My cool label text{hair space}{image attachment}{nonbreaking space}Another piece of label text");
You would pre-generate the testing string via a utility method in BonMot, and then copy and paste it into your unit test. The BONAssertEquivalentStrings()
macro takes two strings, runs the BonMot conversion on the first one, and then XCTAssertEqualObjects
es them to see if they contain the same characters.
Bonus points: generate a useful error message that highlights, via a ^
character on the next line, at which index the two strings first differ.
All the cool kids are using it.
@jvisenti are there things we can do to make BonMot play nice with RZDataBinding? If an attributed string is updated each time one of several properties changes, can we make string re-generation faster by caching or coalescing things?
BonMot should make it easy to strike through part or all of a string. If multiple strikethrough styles are available, those should be supported as well.
A BONText
should be able to specify that its string will be transformed to uppercase, lowercase, and title case.
BONTextAlignmentConstraint
relies (or heavily implies reliance) on the KVO-ability of the font
property of UILabel
, UIButton
, UITextView
, and UITextField
. We should add unit tests to check our assumptions about these properties. We should probably also add KVO for titleLabel.font
for the case of UIButton
.
We should also check whether setting the attributedText
of these objects triggers KVO on font
, and if not, add observation for attributedText
.
BotMot should have a nice logo, preferably one that illustrates its capabilities with attributed strings.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.