Customize color for NSLinkAttributeName in UILabel - ios

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
}

Related

UITextView rich text editing

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

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)

textcolor change while typing in textview ios

I am using textview for colored text typing .
when choosing a color and typing, the color should continue to appear until placing a period
you see how after I placed a period(.) after "hello" the "Hhh" is still red? so I'm saying that after place a period(.) the color should default back to black.
- (BOOL)growingTextView:(HPGrowingTextView *)growingTextView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
if ([text isEqualToString:#"."]) {
txtMessage.textColor=[UIColor blackColor];
}
}
this code made all text in black color.
How can i manage this feature in Textview ?
You should add textViewDidChange method of your UITextview
func textViewDidChange(_ textView: UITextView) { //Handle the text changes here
if textView.text!.lowercased().range(of:".") { // Check your textview string has "." then set black color
// set black color
let splitStringArr = textView.text!.characters.split(separator: ".", maxSplits: 1) // split first match "."
if splitStringArr.count > 0 {
let redString = splitStringArr[0]
let blackString = splitStringArr[1]
if redString.count > 0 {
myMutableString1 = NSMutableAttributedString(string: redString, attributes: [NSFontAttributeName:UIFont(name: "Georgia", size: 18.0)!]) // set font as you like
myMutableString1.addAttribute(NSForegroundColorAttributeName, value: UIColor.redColor(), range:NSRange(location:0,length:redString.count))
}
if blackString.count > 0 {
myMutableString2 = NSMutableAttributedString(string: redString, attributes: [NSFontAttributeName:UIFont(name: "Georgia", size: 18.0)!]) // set font as you like
myMutableString2.addAttribute(NSForegroundColorAttributeName, value: UIColor.blackColor(), range:NSRange(location:0,length:blackString.count))
}
[mutableAttString1 appendAttributedString:newAttString2];
labName.attributedText = myMutableString1
}
else {
// set red color
}
}
}

UITextfield text color is not changing while not in focus

So I have two UITextFields: name and amount and two UIButtons: income, expense.
When I press the expense button I want my amount textfield color to change to red or green if income button is pressed.
This only works if amount textfield is in focus, if name textfield is in focus, the color is not changed for amount.
Is there a way to change the color of the textfield if it's not in focus ?
edit:
Here is my code where I change the color:
#IBAction func typeBtnPressed(_ sender: UIButton) {
if sender.tag == Buttons.expense.rawValue {
amountTxt.textColor = .red
} else {
amountTxt.textColor = .green
}
}
After the textColor is set, the value needs to be reassigned to the textField.
textField.color = newColor
textField.text = text
It seems iOS uses by default attributedText and not text, that is why nothing is happening, and on focus it seems it takes your textColor into account, just do
let color: UIColor
if sender.tag == Buttons.expense.rawValue {
color = .red
} else {
color = .green
}
let attributedText = NSMutableAttributedString(attributedString: amountTxt.attributedText!)
attributedText.setAttributes([NSAttributedStringKey.foregroundColor : color], range: NSMakeRange(0, attributedText.length))
amountTxt.attributedText = attributedText
This will then work as soon as button is pressed
Swift 4 Xcode 10 edition
let attributedText = NSMutableAttributedString(attributedString: textField.attributedText!)
attributedText.setAttributes([NSAttributedString.Key.foregroundColor : UIColor.red], range: NSMakeRange(0, attributedText.length))
textField.attributedText = attributedText

How to set letter spacing of UITextField

I have an app in which the user has to type a four digit pin code. All digits have to be at a set distance from each other.
Is there a way to do this if the PinTextField is a subclass of UIView? I know in a ViewController you could use UITextFieldTextDidChangeNotification and set the attributed text for each change. Notifications don't seem to work in a UIView though.
Also I was wondering if there isn't a simpler way than making an attributed string for every update if you want to set the letter spacing of a UITextField text ?
Correct spacing:
Wrong spacing:
No need to go for attributedText, which to be honest, was a mess implementing with modified spacing. As soon as I closed the keyboard the spacing disappeared, which prompted me to dig further.
Every UITextField has a property called defaultTextAttributes, which according to Apple "returns a dictionary of text attributes with default values.". The Apple document also says that "this property applies the specified attributes to the entire text of the text field"
Just find a suitable place in your code, usually where the textfield is being initialized and then copy and paste the following.
Answered in Swift 3.0
textfield.defaultTextAttributes.updateValue(spacing, forKey: NSKernAttributeName)
where spacing is of CGFloat type. For example 2.0
This works for different fonts as well.
Cheers!!
The latest syntax seems to be:
yourField.defaultTextAttributes.updateValue(36.0,
forKey: NSAttributedString.Key.kern)
This is what eventually worked to set the kern for every change
textField.addTarget(self, action: "textFieldDidChange", forControlEvents: .EditingChanged)
func textFieldDidChange () {
let attributedString = NSMutableAttributedString(string: textField.text)
attributedString.addAttribute(NSKernAttributeName, value: 5, range: NSMakeRange(0, count(textField.text)))
attributedString.addAttribute(NSFontAttributeName, value: font, range: NSMakeRange(0, count(textField.text)))
attributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.blackColor(), range: NSMakeRange(0, count(textField.text)))
textField.attributedText = attributedString
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if count(textField.text) < 4 {
return true
// Else if 4 and delete is allowed
}else if count(string) == 0 {
return true
// Else limit reached
}else{
return false
}
}
The problem however remains because different numbers have different widths, I will just resort back to making a UITextField for every digit.
Use the defaultTextAttributes property of UITextField. It will handle the conversion to NSAttributedString for you and apply the attributes you set. For example:
NSMutableDictionary *attrs = [self.textField.defaultTextAttributes mutableCopy];
[attrs addEntriesFromDictionary:#{
NSKernAttributeName: #18,
NSUnderlineColorAttributeName: [UIColor grayColor],
NSUnderlineStyleAttributeName: #(NSUnderlineStyleSingle | NSUnderlinePatternDash)
}];
self.textField.defaultTextAttributes = attrs;
Try this code After setting the delegate to the textfield.Hope it will work.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:textField.text];
[attributedString addAttribute:NSKernAttributeName
value:#(5.4)
range:NSMakeRange(0, textField.text.length)];
textField.attributedText = attributedString;
return YES;
}
Not really sure about any other solution instead of using attributed string.
But for the notification part, you can set the textFields delegate to UIView and define below method in the view.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string;
The above method is called every time the text entered in the text field changes.
This is working fine in Swift 2.2. Hope this will help you for letter spacing in text field
override func viewDidLoad() {
// Do any additional setup after loading the view.
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(SignupVC.limitTextField(_:)), name: "UITextFieldTextDidChangeNotification", object: txtContactNumber)
}
func limitTextField(Notif:NSNotification) {
let limit=10;
let attributedString = NSMutableAttributedString(string: txtContactNumber.text!)
attributedString.addAttribute(NSKernAttributeName, value: 7, range: NSMakeRange(0, (txtContactNumber.text?.characters.count)!))
// attributedString.addAttribute(NSFontAttributeName, value: font, range: NSMakeRange(0, count(textField.text)))
attributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.blackColor(), range: NSMakeRange(0,(txtContactNumber.text?.characters.count)!))
txtContactNumber.attributedText = attributedString
if(txtContactNumber.text?.characters.count>limit)
{
txtContactNumber.text=txtContactNumber.text?.substringToIndex(limit)
}
}
Need to count the kern for each character and remove it for the last character. There is example on Swift 5.3
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let maxLength = 6
let symbolWidth = CGFloat(43)
let font = UIFont.systemFont(ofSize: 30)
if string == "" { // when user remove text
return true
}
if textField.text!.count + string.count - range.length > maxLength { // when user type extra text
return false
}
let currentText = NSMutableAttributedString(attributedString: textField.attributedText ?? NSMutableAttributedString())
currentText.deleteCharacters(in: range) // delete selected text
var newStringLength = 0
for char in string{
let newSymbol = NSMutableAttributedString(string: String(char))
newSymbol.addAttribute(.font, value: font, range: NSMakeRange(0, 1))
let currentSymbolWidth = newSymbol.size().width
let kern = symbolWidth - currentSymbolWidth
newSymbol.addAttribute(.kern, value: kern, range: NSMakeRange(0,1))
currentText.insert(newSymbol, at: range.location + newStringLength)
newStringLength += 1
}
if currentText.length == maxLength{
currentText.addAttribute(.kern, value: 0, range: NSMakeRange(maxLength - 1, 1))
}
textField.attributedText = currentText
return false
}

Resources