How can I write NSAttributedString in rtf file?
You can use NSAttributedString
's data(from:)
method to convert your attributed string into rtf data.
extension NSAttributedString {
func rtf() throws -> Data {
try data(from: .init(location: 0, length: length),
documentAttributes: [.documentType: NSAttributedString.DocumentType.rtf,
.characterEncoding: String.Encoding.utf8])
}
}
let textView = UITextView()
textView.attributedText = .init(string: "abc",
attributes: [.font: UIFont(name: "Helvetica", size: 16)!])
do {
let rtfURL = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent("RichTextDocument.rtf")
try textView.attributedText.rtf().write(to: rtfURL)
print("saved")
} catch {
print(error)
}
Convert NSAttributedString into Data for storage
You need to specify what kind of document data you would like to convert your attributed string to:
.txt // Plain Text Document Type (Simple Text)
.html // HTML Text Document Type (Hypertext Markup Language)
.rtf // RTF Text Document Type (Rich text format document)
.rtfd // RTFD Text Document Type (Rich text format document with attachment)
update Xcode 10.2 • Swift 5 or later
let textView = UITextView()
textView.attributedText = .init(string: "abc",
attributes: [.font: UIFont(name: "Helvetica", size: 16)!])
if let attributedText = textView.attributedText {
do {
let htmlData = try attributedText.data(from: .init(location: 0, length: attributedText.length),
documentAttributes: [.documentType: NSAttributedString.DocumentType.html])
let htmlString = String(data: htmlData, encoding: .utf8) ?? ""
print(htmlString)
} catch {
print(error)
}
}
Expanding on that:
extension NSAttributedString {
convenience init(data: Data, documentType: DocumentType, encoding: String.Encoding = .utf8) throws {
try self.init(attributedString: .init(data: data, options: [.documentType: documentType, .characterEncoding: encoding.rawValue], documentAttributes: nil))
}
func data(_ documentType: DocumentType) -> Data {
// Discussion
// Raises an rangeException if any part of range lies beyond the end of the receiver’s characters.
// Therefore passing a valid range allow us to force unwrap the result
try! data(from: .init(location: 0, length: length),
documentAttributes: [.documentType: documentType])
}
var text: Data { data(.plain) }
var html: Data { data(.html) }
var rtf: Data { data(.rtf) }
var rtfd: Data { data(.rtfd) }
}
Usage:
let textView = UITextView()
textView.attributedText = .init(string: "abc", attributes: [.font: UIFont(name: "Helvetica", size: 16)!])
if let textData = textView.attributedText?.text {
let text = String(data: textData, encoding: .utf8) ?? ""
print(text) // abc
}
if let htmlData = textView.attributedText?.html {
let html = String(data: htmlData, encoding: .utf8) ?? ""
print(html) // /* <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" ...
}
This will print
abc
/* <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="Content-Style-Type" content="text/css">
<title></title>
<meta name="Generator" content="Cocoa HTML Writer">
<style type="text/css">
p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Helvetica}
span.s1 {font-family: 'Helvetica'; font-weight: normal; font-style: normal; font-size: 16.00pt}
</style>
</head>
<body>
<p class="p1"><span class="s1">abc</span></p>
</body>
</html>
*/
Cocoa: How to save NSAttributedString to JSON
NSAttributedString has two properties:
- the string
- an array of attribute "runs"
Each "run" has:
- an integer range that it applies to
- a dictionary of key/value attributes
It would be very easy to represent that as JSON, using enumerateAttributesInRange:options:usingBlock:
.
Something like:
{
"string" : "Hello World",
"runs" : [
{
"range" : [0,3],
"attributes" : {
"font" : {
"name" : "Arial",
"size" : 12
}
}
},
{
"range" : [3,6],
"attributes" : {
"font" : {
"name" : "Arial",
"size" : 12
},
"color" : [255,0,0]
}
},
{
"range" : [9,2],
"attributes" : {
"font" : {
"name" : "Arial",
"size" : 12
}
}
}
]
}
EDIT: here's an example implementation:
// create a basic attributed string
NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:@"Hello World" attributes:@{NSFontAttributeName: [NSFont fontWithName:@"Arial" size:12]}];
[attStr addAttribute:NSForegroundColorAttributeName value:[NSColor redColor] range:NSMakeRange(3, 6)];
// build array of attribute runs
NSMutableArray *attributeRuns = [NSMutableArray array];
[attStr enumerateAttributesInRange:NSMakeRange(0, attStr.length) options:0 usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) {
NSArray *rangeArray = @[[NSNumber numberWithUnsignedInteger:range.location],
[NSNumber numberWithUnsignedInteger:range.length]];
NSMutableDictionary *runAttributes = [NSMutableDictionary dictionary];
[attrs enumerateKeysAndObjectsUsingBlock:^(id attributeName, id attributeValue, BOOL *stop) {
if ([attributeName isEqual:NSFontAttributeName]) { // convert font values into a dictionary with the name and size
attributeName = @"font";
attributeValue = @{@"name": [(NSFont *)attributeValue displayName],
@"size": [NSNumber numberWithFloat:[(NSFont *)attributeValue pointSize]]};
} else if ([attributeName isEqualToString:NSForegroundColorAttributeName]) { // convert foreground colour values into an array with red/green/blue as a number from 0 to 255
attributeName = @"color";
attributeValue = @[[NSNumber numberWithInteger:([(NSColor *)attributeValue redComponent] * 255)],
[NSNumber numberWithInteger:([(NSColor *)attributeValue greenComponent] * 255)],
[NSNumber numberWithInteger:([(NSColor *)attributeValue blueComponent] * 255)]];
} else { // skip unknown attributes
NSLog(@"skipping unknown attribute %@", attributeName);
return;
}
[runAttributes setObject:attributeValue forKey:attributeName];
}];
// save the attributes (if there are any)
if (runAttributes.count == 0)
return;
[attributeRuns addObject:@{@"range": rangeArray,
@"attributes": runAttributes}];
}];
// build JSON output
NSDictionary *jsonOutput = @{@"string": attStr.string,
@"runs": attributeRuns};
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonOutput options:NSJSONWritingPrettyPrinted error:NULL];
NSLog(@"%@", [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]);
exit(0);
Saving custom attributes in NSAttributedString
The normal way of saving an NSAttributedString
is to use RTF, and RTF data is what the -dataFromRange:documentAttributes:error:
method of NSAttributedString
generates.
However, the RTF format has no support for custom attributes. Instead, you should use the NSCoding
protocol to archive your attributed string, which will preserve the custom attributes:
//asssume attributedString is your NSAttributedString
//encode the string as NSData
NSData* stringData = [NSKeyedArchiver archivedDataWithRootObject:attributedString];
[stringData writeToFile:pathToFile atomically:YES];
//read the data back in and decode the string
NSData* newStringData = [NSData dataWithContentsOfFile:pathToFile];
NSAttributedString* newString = [NSKeyedUnarchiver unarchiveObjectWithData:newStringData];
Read and write a String from text file
For reading and writing you should use a location that is writeable, for example documents directory. The following code shows how to read and write a simple string. You can test it on a playground.
Swift 3.x - 5.x
let file = "file.txt" //this is the file. we will write to and read from it
let text = "some text" //just a text
if let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
let fileURL = dir.appendingPathComponent(file)
//writing
do {
try text.write(to: fileURL, atomically: false, encoding: .utf8)
}
catch {/* error handling here */}
//reading
do {
let text2 = try String(contentsOf: fileURL, encoding: .utf8)
}
catch {/* error handling here */}
}
Swift 2.2
let file = "file.txt" //this is the file. we will write to and read from it
let text = "some text" //just a text
if let dir = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.AllDomainsMask, true).first {
let path = NSURL(fileURLWithPath: dir).URLByAppendingPathComponent(file)
//writing
do {
try text.writeToURL(path, atomically: false, encoding: NSUTF8StringEncoding)
}
catch {/* error handling here */}
//reading
do {
let text2 = try NSString(contentsOfURL: path, encoding: NSUTF8StringEncoding)
}
catch {/* error handling here */}
}
Swift 1.x
let file = "file.txt"
if let dirs : [String] = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.AllDomainsMask, true) as? [String] {
let dir = dirs[0] //documents directory
let path = dir.stringByAppendingPathComponent(file);
let text = "some text"
//writing
text.writeToFile(path, atomically: false, encoding: NSUTF8StringEncoding, error: nil);
//reading
let text2 = String(contentsOfFile: path, encoding: NSUTF8StringEncoding, error: nil)
}
RTF file to attributed string
Rather than looking to see if the operation worked/failed in the debugger, you’d be much better off writing the code to handle the failure appropriately:
if let attributedText = NSAttributedString(fileURL: fileURL, options: [NSDocumentTypeDocumentAttribute:NSRTFTextDocumentType], documentAttributes: nil, error: &error) {
textView.attributedText = attributedText
}
else if let error = error {
println(error.localizedDescription)
}
Swift 4
do {
let attributedString = try NSAttributedString(url: fileURL, options: [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.rtf], documentAttributes: nil)
} catch {
print("\(error.localizedDescription)")
}
Related Topics
iOS - Add Image and Text in Title of Navigation Bar
Codable Class Does Not Conform to Protocol Decodable
Appearance Proxies/Ui_Appearance_Selector in Swift
Add a Border with Cornerradius to an Image in Swiftui Xcode Beta 5
How to Destroy a Singleton in Swift
How to Reset Intent Extension Configurations in Widgetkit
How to Compare Just the Time of a Date in Swift
Scenedidload Being Called Twice
Building a Spritekit/Gamekit Leaderboard Within a Specific Scene
How to Create an Array with Incremented Values in Swift
Why Does Adding 'Dynamic' Fix My Bad Access Issues
How to Set Font Size of Sklabelnode to Fit in Fixed Size (Swift)
Continuously Train Coreml Model After Shipping
How to Link to a 3Rd Party Swift Framework
Nsmanagedobject Changes Do Not Trigger Objectwillchange
Read and Write Permission for User Selected Folder in MAC Os App
Slide Sidebar Menu iOS 8 Swift
Change Time Interval in Skaction.Waitforduration() as Game Goes On