Reputation: 3110
I have a UITextView in an iOS app, and I want to allow the user to bold/italicize/underline/etc at will, so I gave set the allowsEditingTextAttributes property to true on the text view. However, I would also like to allow the user to change things like font and color. Currently, it seems that changing even the color property of the UITextView resets the formatting of the UITextView (i.e. gets rid of the bolding/italics/etc). Is there any way to change this without resetting the formatting? For reference, I am currently doing this to change the color:
self.textView.textColor = colorTheUserPicked //a UIColor picked by the user
EDIT: Actually, this was my bad, I was also resetting the value of the text property itself when I changed the color. Removing that allowed me to change the color as desired. However, I still can't change the font or even the font size without removing the bold/italics/etc. I realize this might be impossible, though...
Upvotes: 1
Views: 4113
Reputation: 15
Implemented similar for C# for Xamarin.IOS.
NSError error = null;
var htmlString = new NSAttributedString(NSUrl.FromFilename(
"About.html"),
new NSAttributedStringDocumentAttributes {DocumentType = NSDocumentType.HTML},
ref error);
var mutableCopy = new NSMutableAttributedString(htmlString);
htmlString.EnumerateAttributes(new NSRange(0, htmlString.Length),
NSAttributedStringEnumeration.LongestEffectiveRangeNotRequired,
(NSDictionary attributes, NSRange range, ref bool stop) =>
{
var newMutableAttributes = new NSMutableDictionary(attributes);
if (newMutableAttributes[UIStringAttributeKey.Font] != null)
{
var currentFont = newMutableAttributes[UIStringAttributeKey.Font] as UIFont;
if (currentFont != null)
{
var newFontName = currentFont.Name;
if (currentFont.Name.Equals("TimesNewRomanPS-BoldItalicMT"))
{
newFontName = "HelveticaNeue-BoldItalic";
}
else if (currentFont.Name.Equals("TimesNewRomanPS-ItalicMT"))
{
newFontName = "HelveticaNeue-Italic";
}
else if (currentFont.Name.Equals("TimesNewRomanPS-BoldMT"))
{
newFontName = "HelveticaNeue-Bold";
}
else if (currentFont.Name.Equals("TimesNewRomanPSMT"))
{
newFontName = "HelveticaNeue";
}
newMutableAttributes.SetValueForKey(UIFont.FromName(newFontName, currentFont.PointSize * 1.5f), UIStringAttributeKey.Font); // = newFont;
}
else
{
newMutableAttributes.SetValueForKey(UIFont.FromName("HelveticaNeue", 14), UIStringAttributeKey.Font); // Default to something.
}
}
mutableCopy.AddAttributes(newMutableAttributes, range);
});
aboutTextView.AttributedText = mutableCopy;
Upvotes: 1
Reputation: 3110
Thanks @Larme! Your solution is correct, so I accepted it! Here is the Swift version for reference:
//needs to be a mutable attributed string
var mutableCopy = NSMutableAttributedString(attributedString: textView.attributedText)
//make range
var textRange = NSMakeRange(0, textView.attributedText.length)
//get all the string attributes
textView.attributedText.enumerateAttributesInRange(textRange, options: NSAttributedStringEnumerationOptions.LongestEffectiveRangeNotRequired, usingBlock: { (attributes, range, stop) -> Void in
//make a copy of the attributes we can edit
var newAttributes = attributes as [NSObject : AnyObject]
if newAttributes[NSFontAttributeName] != nil { //if the font attr exists
//create a new font with the old font name and new size
let currentFont = newAttributes[NSFontAttributeName] as UIFont
let newFont = UIFont(name: currentFont.fontName, size: currentFont.pointSize * 0.5) //set new font size to half of old size
//replace the nsfontattribute's font with the new one
newAttributes[NSFontAttributeName] = newFont
}
//replace the old attributes with the new attributes to the mutable version
mutableCopy.addAttributes(newAttributes, range: range)
})
//replace the old attributed text with the newly attributed text
textView.attributedText = mutableCopy
Upvotes: 1
Reputation: 26016
I'll answer with an Objective-C solution since I don't code in Swift, but it should be translated easily into Swift.
NSAttributedString
"effects" are stored in a NSDictionary
. So it's a Key:Value system (with unicity of Key).
Bold/Italic (from your example) are inside the NSFontAttributedName
's value. That's why you can't set it again like this.
The main key is to use enumerateAttributesInRange:options:usingBlock:
:
[attr enumerateAttributesInRange:NSMakeRange(0, [attr length])
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
usingBlock:^(NSDictionary *attributes, NSRange range, BOOL *stop)
{
NSMutableDictionary *newAttributes = [attributes mutableCopy];
//Do changes
[attr addAttributes:newAttributes range:range]; //Apply effects
}];
The following lines are at "//Do changes" place. So, if you want to change the Font size:
if ([newAttributes objectForKey:NSFontAttributeName])
{
UIFont *currentFont = (UIFont *)[attributes objectForKey:NSFontAttributeName];
UIFont *newFont = [UIFont fontWithName:[currentFont fontName] size:[currentFont pointSize]*0.5];//Here, I multiplied the previous size with 0.5
[newAttributes setValue:newFont forKey:NSFontAttributeName];
}
If you want to bold/italic, etc. the font you may be interested in UIFontDescriptor
, and a related question.
It shows you how to get the previous font and change get a new font with some commons with the previous one.
If you want to change the color:
if ([newAttributes objectForKey:NSForegroundColorAttributeName])//In case if there is some part of the text without color that you don't want to override, or some custom- things
{
UIColor *newColor = [UIColor blueColor];
[newAttributes setValue:newColor forKey:NSForegroundColorAttributeName];
}
The underline effect is in NSUnderlineStyleAttributeName
's value. So analogic changes can be done.
Upvotes: 2