UITextView rich text editing - ios

I need to implement a text editor using UITextView that supports:
Bold/Italic/Underline
Color,Font,font size changes
Paragraph alignment
List format (bullets, numbers, etc.)
Custom selection of text anywhere in the text view and change the properties
So far I have managed to do it without NSTextStorage but it seems I am hitting limits. For instance, to change font, I use UIFontPickerViewController and change the font as follows:
func fontPickerViewControllerDidPickFont(_ viewController: UIFontPickerViewController) {
if let selectedFontDesc = viewController.selectedFontDescriptor {
let font = UIFont(descriptor: selectedFontDesc, size: selectedFontDesc.pointSize)
self.selectedFont = font
self.textView.typingAttributes = [NSAttributedString.Key.foregroundColor: self.selectedColor ?? UIColor.white, NSAttributedString.Key.font: self.selectedFont ?? UIFont.preferredFont(forTextStyle: .body, compatibleWith: nil)]
if let range = self.textView.selectedTextRange, let selectedFont = selectedFont {
let attributedText = NSMutableAttributedString(attributedString: self.textView.attributedText)
let location = textView.offset(from: textView.beginningOfDocument, to: range.start)
let length = textView.offset(from: range.start, to: range.end)
let nsRange = NSRange(location: location, length: length)
attributedText.setAttributes([NSAttributedString.Key.font : selectedFont], range: nsRange)
self.textView.attributedText = attributedText
}
}
}
This works but the problem is it resets the color of the selected text and other properties. I need to understand a way in which the existing attributed of the text under selection are not disturbed. I suspect the way to do is with using NSTextStorage but I can't find anything good on internet that explains the right use of NSTextStorage to achieve this.

The problem is this call:
attributedText.setAttributes...
This, as you have observed, makes the attribute you provide (here, the font) the only attribute of this range. Instead, you want to add your font attribute to the existing attributes:
https://developer.apple.com/documentation/foundation/nsmutableattributedstring/1414304-addattributes

Related

How to add a small image on right side of text whatever font size it is?

I'm trying to achieve this on iOS using Swift and storyboards:
Notice that the image size on the right never changes, only the text font size changes depending on the user's default font size. I want the image to remain at the end of the label.
Does anyone have an idea how I can do it?
The simplest is to create an extension on UILabel and append whatever image/icon to the end of the attributedText, something along these lines:
extension UILabel {
func appendIcon() {
let iconAttachment = NSTextAttachment()
iconAttachment.image = UIImage(systemName: "info.circle.fill")
let iconString = NSAttributedString(attachment: iconAttachment)
guard let attributedText = self.attributedText else { return }
let currentString = NSMutableAttributedString(attributedString: attributedText)
currentString.append(iconString)
self.attributedText = currentString
}
}
And then call that programmatically.
label.appendIcon() // will need to create outlet

After changing the preferred content size, the font size in some of my reusable cells' labels are still unchanged

I'm using a UICollectionView with compositional layout that contains UICollectionViewListCells. Each cell contains several UILabels. I set the dynamic type with custom font weight with
extension UIFont {
static func preferredFont(for style: TextStyle, weight: Weight) -> UIFont {
let metrics = UIFontMetrics(forTextStyle: style)
let desc = UIFontDescriptor.preferredFontDescriptor(withTextStyle: style)
let font = UIFont.systemFont(ofSize: desc.pointSize, weight: weight)
return metrics.scaledFont(for: font)
}
}
source: https://mackarous.com/dev/2018/12/4/dynamic-type-at-any-font-weight
I then change the font size:
When I scroll through my UICollectionView, some cells' font size remain unchanged while most are correctly resized (everything works perfectly when using something simpler like UIFont.preferredFont(forTextStyle: .subheadline). What is the best approach to getting the proper font size to render in response to the change in preferred content size?
Additionally I'm creating an attributed string which contains SF Symbols https://stackoverflow.com/a/58341241 and setting the size of the symbol with
let configuration = UIImage.SymbolConfiguration(textStyle: .title1)
When I change the font size, the image doesn't dynamically scale. Both of these issues go away when I restart the app. Is there any code I can execute when UIContentSizeCategory.didChangeNotification posts, or other approaches to tackling these two issues?
EDIT
I was able to solve the first part of the question by using the approach laid out here https://spin.atomicobject.com/2018/02/02/swift-scaled-font-bold-italic/.
extension UIFont {
func withTraits(traits:UIFontDescriptor.SymbolicTraits) -> UIFont {
let descriptor = fontDescriptor.withSymbolicTraits(traits)
return UIFont(descriptor: descriptor!, size: 0) //size 0 means keep the size as it is
}
func bold() -> UIFont {
return withTraits(traits: .traitBold)
}
}
let boldFont = UIFont.preferredFont(forTextStyle: .headline).bold()
I'm still stuck on how to get the sf symbol UIImage in the attributed string to resize when the preferred content size changes.
To get the attributed string to resize when the preferred content size changes.
func attributedStringDynamicTypeExample() {
let font = UIFont(name: "Times New Roman", size: 20)!
let fontMetrics = UIFontMetrics(forTextStyle: .title3)
let scaledFont = fontMetrics.scaledFont(for: font)
let attachment = NSTextAttachment()
attachment.image = UIImage(named: "BigPoppa")
attachment.adjustsImageSizeForAccessibilityContentSizeCategory = true
let attributes: [NSAttributedString.Key: Any] = [.font: scaledFont]
let attributedText = NSMutableAttributedString(string: "Your text here!", attributes: attributes)
attributedText.append(NSAttributedString(attachment: attachment))
label.adjustsFontForContentSizeCategory = true
label.attributedText = attributedText
}
As far as the best approach to getting the proper font size to render in response to the change in preferred content size I prefer overriding the traitCollectionDidChange like so:
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
guard previousTraitCollection?.preferredContentSizeCategory
!= traitCollection.preferredContentSizeCategory
else { return }
collectionView.collectionViewLayout.invalidateLayout()
}
But using the notification should be just as effective:
NotificationCenter.default.addObserver(self,
selector: #selector(sizeCategoryDidChange),
name: UIContentSizeCategory.didChangeNotification,
object: nil)
#objc private func sizeCategoryDidChange() {
collectionView.collectionViewLayout.invalidateLayout()
}

How to keep strikethrough when adding a new one in a textview? The strikethrough jumps to the newest highlighted word

I have a textview and it's set as an attributed text. When I use this code and click my button to strikethrough it will strikethrough the highlighted section perfectly. But when I highlight another section and click strikethrough it clears the first one and the new highlighted section I strikethrough gets the line.
let attributeString: NSMutableAttributedString = NSMutableAttributedString(string: textView.text)
attributeString.addAttribute(NSAttributedString.Key.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: textView.selectedRange)
textView.attributedText = attributeString
textView.font = UIFont(name: "Helvetica Neue", size: 16.0)
How can I keep it for all sections? Where it won't clear out unless I clear it myself? I guess I didn't realize how would I clear it also?
You create a new attributed string with no attributes, then you add the one strikethrough.
Instead, update the existing attributed string.
let attributeString: NSMutableAttributedString = NSMutableAttributedString(attributedString: textView.attributedText)

Customize color for NSLinkAttributeName in UILabel

I want to customize color for NSLinkAttributeName in UILabel. But setting NSForegroundColorAttributeName not affect link text color, it still blue.
But NSUnderlineColorAttributeName works and I was able to customize underline color. Is it possible to change link text color somehow?
I also had same issue when I tried to customize UILabel, and I figured, that NSLinkAttributeName has bigger priority than NSForegroundColorAttributeName. Or, maybe, NSLinkAttributeName processed after foreground color.
I ended with cycle through all NSLinkAttributeName and replace it with my custom attribute with name CustomLinkAttribute. After that it works like a charm. And I was also able to get link, by accessing to my custom attribute
func setupHtmlLinkTextStyle(attributedString: NSAttributedString) -> NSAttributedString {
let updatedString = NSMutableAttributedString(attributedString: attributedString)
attributedString.enumerateAttribute(NSLinkAttributeName,
in: NSRange(location: 0, length: attributedString.length),
options: [],
using:
{(attribute, range, stop) in
if attribute != nil {
var attributes = updatedString.attributes(at: range.location, longestEffectiveRange: nil, in: range)
attributes[NSForegroundColorAttributeName] = UIColor.green
attributes[NSUnderlineColorAttributeName] = UIColor.green
attributes[NSStrokeColorAttributeName] = UIColor.green
attributes["CustomLinkAttribute"] = attribute!
attributes.removeValue(forKey: NSLinkAttributeName)
updatedString.setAttributes(attributes, range: range)
}
})
return updatedString
}

How to set a global Place holder colour for a UITextField for the whole app in swift?

I am creating a app in which i use several UITextfield's. I know how to change the placeholder colour of a single text field.
textField.attributedPlaceholder = NSAttributedString(string: "Username", attributes: [NSForegroundColorAttributeName: UIColor.redColor()]).
But i want to change the placeholder colour of all the UITextFields in the whole app. My app has more than 50 UIViewControllers and more than 25 of them has textFields(2 to 22 per screen). I want a code that can be used globally in one place so that i don't need to go to every view controller and change it manually.
If you have any other alternatives to make the job done please let me know.
I am using xcode 7.1.1 swift 2.0
Update:
For default the Placeholder colour is set to light grey colour. Is there any way for us to tweak that default behaviour and change it to any other colour?
How can we access this default code and change it?
Create the extension method
extension String {
func toAttributedString(font font:UIFont!, kerning: CGFloat!, color:UIColor!) -> NSAttributedString {
return NSAttributedString(string: self as String, font: font, kerning: kerning, color: color)!
}
}
extension NSAttributedString {
convenience init?(string text:String, font:UIFont!, kerning: CGFloat!, color:UIColor!) {
self.init(string: text, attributes: [NSKernAttributeName:kerning, NSFontAttributeName:font, NSForegroundColorAttributeName:color])
}
}
Example usage
/ Example Usage
var testString: String = "Hi Kautham"
var testAttributedString: NSAttributedString = testString.toAttributedString(font: UIFont.boldSystemFontOfSize(20), kerning: 2.0, color: UIColor.whiteColor())
let label = UILabel(frame: CGRect(x: 50, y: 50, width: 200, height: 20))
label.attributedText = testAttributedString
self.view.addSubview(label)
Declare a Macro with your NSAttributedString.
Give that Attributed string to all textfields wherever you want.
So that when you change in Macro, will reflect in all places..
Check out the following.
let CommonTextFieldAttr : NSDictionary = [NSForegroundColorAttributeName:UIColor.redColor()]
And Use it in Textfield properties.
textField?.attributedPlaceholder = NSAttributedString(string: "sdfa",attributes: CommonTextFieldAttr as? [String : AnyObject])
Hope it helps..

Resources