Are there any analogues of [NSString stringWithFormat:] for NSAttributedString
Here is a Swift 4 extension based on TheJeff's answer (corrected for multiple substitutions). It is restricted to substituting placeholders with NSAttributedString's:
public extension NSAttributedString {
convenience init(format: NSAttributedString, args: NSAttributedString...) {
let mutableNSAttributedString = NSMutableAttributedString(attributedString: format)
var nsRange = NSString(string: mutableNSAttributedString.string).range(of: "%@")
var param = 0
while nsRange.location != NSNotFound {
guard args.count > 0, param < args.count else {
fatalError("Not enough arguments provided for \(format)")
}
mutableNSAttributedString.replaceCharacters(in: nsRange, with: args[param])
param += 1
nsRange = NSString(string: mutableNSAttributedString.string).range(of: "%@")
}
self.init(attributedString: mutableNSAttributedString)
}
}
I was looking for good existing solution for this question, but no success.
So I was able to implement it on my own.
That's why I am self-answering the question to share the knowledge with community.
Solution
NSAttributedString+VPAttributedFormat category provides methods for building attributed string based on attributed format and arguments that should satisfy this format.
The most suitable case of using this category is text controls with variable attributed text configured in interface builder.
You need set correct string format to attributed text and configure necessary attributes.
Then you need pass necessary arguments in code by using methods of this category.
- Format syntax is the same as in
[NSString stringWithFormat:]
method; - Can be used in Objective C and Swift code;
- Requires iOS 6.0 and later;
- Integrated with CocoaPods;
- Covered with unit tests.
Usage
1. Import framework header or module
// Objective C
// By header
#import <VPAttributedFormat/VPAttributedFormat.h>
// By module
@import VPAttributedFormat;
// Swift
import VPAttributedFormat
2. Set correct format and attributes for text control in interface builder
3. Create IBOutlet and link it with text control
// Objective C
@property (nonatomic, weak) IBOutlet UILabel *textLabel;
// Swift
@IBOutlet weak var textLabel: UILabel!
4. Populate format with necessary arguments
// Objective C
NSString *hot = @"Hot";
NSString *cold = @"Cold";
self.textLabel.attributedText = [NSAttributedString vp_attributedStringWithAttributedFormat:self.textLabel.attributedText,
hot,
cold];
// Swift
let hot = "Hot"
let cold = "Cold"
var arguments: [CVarArgType] = [hot, cold]
textLabel.attributedText = withVaList(arguments) { pointer in
NSAttributedString.vp_attributedStringWithAttributedFormat(textLabel.attributedText, arguments: pointer)
}
5. See result
Examples
VPAttributedFormatExample is an example project. It provides Basic and Pro format examples.
Here's a category I wrote to add the method to NSAttributedString. You'll have to pass in NULL as the last argument to the function however, otherwise it will crash to the va_list restrictions on detecting size. [attributedString stringWithFormat:attrFormat, attrArg1, attrArg2, NULL];
@implementation NSAttributedString(stringWithFormat)
+(NSAttributedString*)stringWithFormat:(NSAttributedString*)format, ...{
va_list args;
va_start(args, format);
NSMutableAttributedString *mutableAttributedString = (NSMutableAttributedString*)[format mutableCopy];
NSString *mutableString = [mutableAttributedString string];
while (true) {
NSAttributedString *arg = va_arg(args, NSAttributedString*);
if (!arg) {
break;
}
NSRange rangeOfStringToBeReplaced = [mutableString rangeOfString:@"%@"];
[mutableAttributedString replaceCharactersInRange:rangeOfStringToBeReplaced withAttributedString:arg];
}
va_end(args);
return mutableAttributedString;
}
@end
Compatible with Swift 4.2
public extension NSAttributedString {
convenience init(format: NSAttributedString, args: NSAttributedString...) {
let mutableNSAttributedString = NSMutableAttributedString(attributedString: format)
args.forEach { (attributedString) in
let range = NSString(string: mutableNSAttributedString.string).range(of: "%@")
mutableNSAttributedString.replaceCharacters(in: range, with: attributedString)
}
self.init(attributedString: mutableNSAttributedString)
}
}
Usage:
let content = NSAttributedString(string: "The quick brown %@ jumps over the lazy %@")
let fox = NSAttributedString(string: "fox", attributes: [.font: Fonts.CalibreReact.boldItalic.font(size: 40)])
let dog = NSAttributedString(string: "dog", attributes: [.font: Fonts.CalibreReact.lightItalic.font(size: 11)])
attributedLabel.attributedText = NSAttributedString(format: content, args: fox, dog)
Result: