NSAttrbutedText object output's a raw string instead of rich text in my NSTextField - objective-c

I have an NSTextField in my XIB, and I created a NSAttributedString instance, and used initWithHTML:documentAttributes: with an NSData object of some html with bold and italic etc.
I used setAttributedText and it outputs plain text of the object:
I am using PyObj-C bridge for my code, so here's my code:
html = """<p><b>Word</b> - English</p>
<p><i>Phonetics</i> - Noun</p>
<p><i>A love this word!</i></p>
<p>Definition - An abstraction</p>
"""
new_html = str.encode(html) # Turn html into byte code for NSData
# Make an NSData object with the html string in it
html = Cocoa.NSData.alloc().initWithBytes_length_(new_html, len(new_html))
# Make an instance of an Attributed String
attrString = Foundation.NSAttributedString.alloc().init()
# Instantiate attributed string with the NSData html string
definition = attrString.initWithHTML_documentAttributes_(html, None)
self.definitionField.setAttributedStringValue_(definition)
Am I doing something wrong? I've looked everywhere on the web, cant seem to find a forum post with my problem too.

The second argument of initWithHTML_documentAttributes_ is a pass-by-reference in-out argument in Objective-C. Because of this the Python version of this method returns a tuple of two values: the actual return value and the output version of the second argument.
To get the behaviour you want use:
definition, _ = Foundation.NSAttributedString.initWithHTML_documentAttributes_(html, objc.NULL)
This also sets the second argument to objc.NULL instead of None to tell the method that your not interested in getting a dict with attributes.
Some other notes:
You're first initialising the attributed with the init method, and then reinitialise it. That's not documented as a valid way to initialise the object, it is better to call just one init* method.
It is not necessary to explicitly create an NSData object, PyObjC proxies instances of bytes as NSData objects. This simplifies your code.

Related

Do instance references really work in Swift?

I wrote the Objective-C code first
NSMutableString *aStrValue = [NSMutableString stringWithString:#"Hello"];
NSMutableDictionary *aMutDict = [NSMutableDictionary dictionary];
[aMutDict setObject:aStrValue forKey:#"name"];
NSLog(#"Before %#",aMutDict);
[aStrValue appendString:#" World"];
NSLog(#"After %#",aMutDict);
I got the output as follows
2015-09-17 14:27:21.052 ShareIt[4946:129853] Before {
name = Hello;
}
2015-09-17 14:27:21.057 ShareIt[4946:129853] After {
name = "Hello World";
}
Means when I append a string to a Mutable string which is actually referred into a MutableDictionary, the change is getting reflected in Dictionary too..
But then I tried something same in Swift
var stringValue:String?
stringValue = "Hello"
var dict:Dictionary = ["name":stringValue!]
println(dict)
stringValue! += " World"
stringValue!.extend(" !!!!")
println(dict)
I seen the output in playground like this
My Questions are
Why the value that changed is not reflecting in a data structure like
Dictionary.
Does in Swift adding any key value really keeps the value or its
reference, if it's keeping the reference like objective-C then here what is my mistake?
Reference type
The different behaviours depends on the fact that in the Objective-C code you use NSMutableString that is a class.
This means that aMutDict and aStrValue are references to the same object of type NSMutableString. So the changes you apply using aStrValue are visibile by aMutDict.
Value type
On the other hand in Swift you are using the String struct. This is a value type. This means that when you copy the value from one variable to another, the change you do using the first variable are not visible to the second one.
The following example clearly describes the value type behaviour:
var word0 = "Hello"
var word1 = word0
word0 += " world" // this will NOT impact word1
word0 // "Hello world"
word1 // "Hello"
Hope this helps.
Strings in Swift (copy by value) are completely different than string in Objective C (copy by reference).
From Apple' Swift documentation:
Strings Are Value Types
Swift’s String type is a value type. If you create a new String value,
that String value is copied when it is passed to a function or method,
or when it is assigned to a constant or variable. In each case, a new
copy of the existing String value is created, and the new copy is
passed or assigned, not the original version. Value types are
described in Structures and Enumerations Are Value Types.
Swift’s copy-by-default String behavior ensures that when a function
or method passes you a String value, it is clear that you own that
exact String value, regardless of where it came from. You can be
confident that the string you are passed will not be modified unless
you modify it yourself.
Behind the scenes, Swift’s compiler optimizes string usage so that
actual copying takes place only when absolutely necessary. This means
you always get great performance when working with strings as value
types.
In swift, String is a Struct.
Structs are not reference types in Swift, thus it's copied when you setting it to a dictionary.

NSKeyedArchiver and AttributedString [duplicate]

I am trying to store an NSAttributedString to a Core Data SQL store.
I have the property set as a "transformable", it is optional and it is NOT transient or indexed and the value transformer name is set to default "NSKeyedUnarchiveFromData". In the .xcdatamodel and generated the managed object class which has this in the .h:
#property (nonatomic, retain) id Text; (I have tried changing id to NSAttributedString *Text)
and this in the .m:
#dynamic Text;
I look through and set the ".text" property of my NSManagedObject to the attributed string then when completed I do:
NSError *error = nil;
[managedObjectContext save:&error];
This through is causing this error in the output:
[NSCFType encodeWithCoder:]:
unrecognized selector sent to instance
0xc04edb0 Terminating app due to
uncaught exception
'NSInvalidArgumentException', reason:
'* -[NSCFType encodeWithCoder:]:
unrecognized selector sent to instance
0xc04edb0'
I have checked the class of what I am storing to the property and it is NSAttributedString also I check responsesToSelector #selector(:) and this returns true so very confused as this is contrary to the error message?
Please advise.
Thanks
James
For anyone experiencing this problem I found the easiest solution:
In Core Data add an attribute, let's call it attributedText. Then define its type as Transformable. After you create the .h file, change the data type of attributedText from NSDictionary to NSAttributedString.
Now you can save the NSAttributedString in Core Data with no modification needed.
Recalling it is as easy as going:
myObject.attributedText
which will return your NSAttributedString!
Hope this helps someone.
I was checking the Apple Developer Forums and found a thread almost exactly the same as this question, one person had done this but unfortunately did not share the code. All they said was the following:
"In Core Data i have an transformable in the database and i us my own NSVauleTransformer. This is a subclass of NSValueTransformer and creates an attributed string from the data object and back.
Therefore i created a class called PersistableAttributedString which is NSCoding compliant. This class has a string and an array of attributes and builds the attributed string. I also created a class for the possible text attributes which is NSCoding compliant. Together the string and the attributes are all NSCoding compliant.
The class NSAttributedString is also NSCoding compliant, but the attributes are not, that's the problem."
Hope that might help.
In Xcode 10 with automatic code generation this is a lot simpler than the other suggestions.
Select the name of the Attribute and open the Data Model inspector (Command+Option+3)
Set Attribute Type to Transformable
Set Custom Class to NSAttributedString
And that's it, now you can just save your data in your Swift code as you'd expect, e.g.:
detailItem.content = textView.attributedText
Another idea would be to create a Custom Class of NSAttributedString and somewhere use enumerateAttributesInRange:options:usingBlock: to get all the attributes of the string and then save the NSDictionary with the attributes and ranges in to Core Data aswell as the attributed string stripped of it's attributes.
Then when the string is loaded again you could apply the attributes that are in the dictionary to the attributed string using initWithString:attributes:.
It's the font that's giving you grief - a CTDictionary is toll-free bridged to NSDictionary which implements NSCoding so should encode fine.
You might have to deal with the font yourself :( - here's a sucky way of doing it.
1) Instead of storing the NSAttributedString, break it down and put each of it's components into an array.
2) Go through the array - if you see font ref you must store just the information required to re-create this font - have a look at the CTFontCopyFontDescriptor function and the CTFontDescriptorCopyAttribute function should let you get font attributes as a string. Put all these into a NSDictionary which should store in core data fine.
3) Store this array in core data - hopefully all the items in the array will be NSCoding compliant so you should be fine.
...
To recreate your string, when you load from coredata, if you see an NSDctionary representing font attributes you should be able to re-create the fCTFontDescriptor and from that the font.
Then, put your string back together.
I found a way to save attributed text in Swift 4 that does not use a header file. My Core Data store consists of an entity called "AttrNote" with attributes of "title" and "notes". "title" is of type "String" but "notes" is of type "transformable". Then within the view controller where note entry/editing is done I have the following for the save button:
#IBAction func saveBtn(_ sender: Any) {
var note: AttrNote!
//other code
if let title = titleTextField.text {
note.title = title
}
if let noteText = notesTextView.attributedText {
note.notes = noteText
}
And the function that is called to load the data has the following:
func loadNoteData() {
if let note = noteToEdit {
titleTextField.text = note.title
notesTextView.attributedText = note.notes as! NSAttributedString
}
}
And I have the following in ViewDidLoad that enables the B/I/U formatting options to appear with the selection of text:
notesTextView.allowsEditingTextAttributes=true
I am able to save attributed text and then view/edit it later.
OK... Some kind of break through although not a good one...
If we NSLog the attributed string then we can see in there it has NSFont and NSParagraphStyle in. Although these are NOT NSFont and NSParagraphStyle in the code these are CTFontRef and CT Dictionaries of paragraph styles... These are not NS objects although in NSLog they output as these and therefore guess that is why we can not perform the "encodeWithCoder" selector on the object.
IF in the code we just do "NSFont;" the compiler says "NSFont undeclared" so what can we do as we only have the CT functions?
As my colleague said in the comments above if we set the ".text" property to just "NSAttrinutedString *string = [NSAttributedString alloc] initWithString:#"test"] it saves fine and if we remove all the styling from the one we WANT to save it also works!
THERE MUST BE A WAY OF STORING NSATTRIBUTED STRING WITH STYLING INTO CORE DATA... NO?
Any ideas greatly appreciated.
-JM

Decoding ‘ in objective c

Im saving data from a WebService and some of the info has ASCII caracters like ‘ or #038;
At the begining I thought that setting a string with a text containing caracacters like those would work perfectly since there are other, like accents \u00f3, that appear decoded in the UILabels but those 2 for example don't.
Why is this? Am I understanding correctly?
Thanks.
Replacing HTML entities
Since you are saving data from a WebService, you can use this NSString category for HTML as described in this post: Objective-C: How to replace HTML entities?
(Html entities are stuff like: ‘ - More on wikipedia)
The methods available in the category are:
- (NSString *)stringByConvertingHTMLToPlainText;
- (NSString *)stringByDecodingHTMLEntities;
- (NSString *)stringByEncodingHTMLEntities;
- (NSString *)stringWithNewLinesAsBRs;
- (NSString *)stringByRemovingNewLinesAndWhitespace;
Response to comments
[NSAttributedString initWithHTML: documentAttributes:]
Initializes and returns a new NSAttributedString object from HTML contained in the given data object.
NSData* htmlData = [yourHTMLString dataUsingEncoding:NSUTF8StringEncoding]
NSAttributedString* theAttributedString = [[NSAttributedString alloc] initWithHTML:htmlData documentAttributes:NULL];
I use this all the time and it works perfectly. I even made a macro out of it:
#define html2AttributedString(htmlString) \
[[[NSAttributedString alloc] initWithHTML:[(htmlString) dataUsingEncoding:NSUTF8StringEncoding] \
documentAttributes:NULL] autorelease]
Usage:
NSAttributedString* attributedString = html2AttributedString(yourNSString);
And then, you can make macro for color, alignment, font, etc…
For iOS, you can check out this page in which they give you a replacement.
Our initWithHTML methods aim to perfectly match the output from the
Mac version. This is possible to achieve for characters and I have
unit tests in place that make certain this keeps being perfect.
Regarding the attributes there are many things that have to be done
slightly different on iOS to achieve the same look. I won’t bother you
with the details there.

difficulty understanding syntax

In the tutorial there is the following declaration:
-(BOOL)writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile;
I'm interpreting this as writeToFile method returns a BOOL result. It takes 2 parameters. The first is an NSString reference. The second is the result of calling atomically and passing it a BOOL value.
Did I get that right?
The tutorial goes on to say you call the above method like this:
BOOL result = [myData writeToFile:#"/tmp/log.txt" atomically:NO];
which is find. But I wonder if I MUST use "atomically:NO"
Could I have done something like
resultOfAtomically = atomically:NO
BOOL result = [ myData writeToFile:#"/tmp/log.txt" resultOfAtomically ];
assuming I declared resultOfAtomically properly.
Also, does prepending # to "/tmp/log.txt" mean something like "give me the reference NOT the value" ?
The text atomically introduces the second parameter of the writeToFile:atomically: method, it is not a separate function. Therefore, you cannot call simply atomically:NO. This would be correct, however:
BOOL myBool = NO;
BOOL result = [myData writeToFile:#"/tmp/log.txt" atomically:myBool];
When talking about Objective-C methods, you would not call this method "writeToFile", you would call it "writeToFile:atomically:". By this syntax you know that the method expects two parameters (one for each colon).
Here's a way to think about how the method declaration breaks down:
-(BOOL)writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile;
- = defining an instance method (+ would be for a class method).
(BOOL) = returning a boolean value.
write = this method is about writing something (nothing magic, just a friendly word choice - could have been print or something else).
ToFile:(NSString *)path = the first parameter of the method, path, is an NSString pointer and the friendly text "ToFile" (again just a word choice, nothing special) hints that I need to provide a file path for that parameter.
atomically:(BOOL)useAuxiliaryFile; = the second parameter of the method, useAuxiliaryFile, is a boolean and the friendly text "atomically" (still nothing special) hints that the boolean value determines whether or not to write the file atomically.

Need help in understanding objective c code

I need some help in understanding the following code:
What is the meaning of '#' in #"Reload"
button = MakeTestButton(&button_rect, #"Reload", content);
[button setTarget:web_view];
[button setAction:#selector(reload:)];
Where I can find the definition of "#selector(reload:)"?
String constants are declared as #"some text" in objective-c. This creates an instance of NSString.
I recommend you read Apple's documentation on selectors. Basically, #selector(reload:) will get a pointer to the method that will be called when an objects receives a reload: message.
The # sign indicates to the compiler that the string is an NSString instead of a standard "C" string. This is a shortcut for creating NSString objects.
See Explanation of Cocoa #selector usage
#selector is a built in primitive in the language. Think of #selector(reload:) as “the name of the method reload:”. It returns a SEL, which you can then pass to a function and later use it to call the method reload:. In the context of your code, when you click the button, the button will call [web_view reload:self].
In #"Reload", the # means that it's an NSString instance instead of a char const *.