How to check if text is underlined - swift

I am struggling to determine if some selected text in a UITextView is underlined. I can quite easily check for bold, italics etc with the following code:
let isItalic = textView.font!.fontDescriptor.symbolicTraits.contains(.traitItalic)
However, I can't figure out how to check for underline?

I have just created a sample project and I think you could do something like the following:
class ViewController: UIViewController {
#IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
let attrText1 = NSMutableAttributedString(string: "TestTest", attributes: [.foregroundColor : UIColor.systemTeal, .underlineStyle: NSUnderlineStyle.single.rawValue])
let attrText2 = NSAttributedString(string: " - not underlined", attributes: [.foregroundColor : UIColor.red])
attrText1.append(attrText2)
textView.attributedText = attrText1
}
func isTextUnderlined(attrText: NSAttributedString?, in range: NSRange) -> Bool {
guard let attrText = attrText else { return false }
var isUnderlined = false
attrText.enumerateAttributes(in: range, options: []) { (dict, range, value) in
if dict.keys.contains(.underlineStyle) {
isUnderlined = true
}
}
return isUnderlined
}
#IBAction func checkButtonDidTap(_ sender: UIButton) {
print(isTextUnderlined(attrText: textView.attributedText, in: textView.selectedRange))
}
}
Create an extension to get the selectedRange as NSRange:
extension UITextInput {
var selectedRange: NSRange? {
guard let range = selectedTextRange else { return nil }
let location = offset(from: beginningOfDocument, to: range.start)
let length = offset(from: range.start, to: range.end)
return NSRange(location: location, length: length)
}
}

I believe underline is not part of the font traits, it must rather be an attribute to the text. You might find the answer to this question useful. I hope it helps you! Enumerate over a Mutable Attributed String (Underline Button)

func checkForUnderline(){
let allWords = self.testView.text.split(separator: " ")
for word in allWords {
let result = self.isLabelFontUnderlined(textView: self.testView,
subString: word as NSString)
if(result == true){
print(word+" is underlined")
}else{
print(word+" is not underlined")
}
}
}
func isLabelFontUnderlined (textView: UITextView, subString:
NSString) -> Bool {
let nsRange = NSString(string: textView.text).range(of: subString as
String, options: String.CompareOptions.caseInsensitive)
if nsRange.location != NSNotFound {
return self.isLabelFontUnderlined(textView: textView,
forRange: nsRange)
}
return false
}
func isLabelFontUnderlined (textView: UITextView, forRange: NSRange) ->
Bool{
let attributedText = testView.attributedText!
var isRangeUnderline = false
attributedText.enumerateAttributes(in: forRange,
options:.longestEffectiveRangeNotRequired) { (dict, range, value) in
if dict.keys.contains(.underlineStyle) {
if (dict[.underlineStyle] as! Int == 1){
isRangeUnderline = true
} else{
isRangeUnderline = false
}
}else{
isRangeUnderline = false
}
}
return isRangeUnderline
}

Related

Change a specific words color in a UITextView

What I'm Trying
Code
func textViewDidChange(_ textView: UITextView) {
var output = " " + textView.text
var outArray = output.components(separatedBy: " ").dropFirst()
for W in outArray {
if W == "//" {
textView.textColor = UIColor.lightGray
}
}
}
Problem
The problem I have is that I am trying to change the color of only a specific words (such as "//" would be red and "let" would be green) in the UITextView, so I was wondering how you might do that.
Try this code
import UIKit
extension String {
func getRanges(of string: String) -> [NSRange] {
var ranges:[NSRange] = []
if contains(string) {
let words = self.components(separatedBy: " ")
var position:Int = 0
for word in words {
if word.lowercased() == string.lowercased() {
let startIndex = position
let endIndex = word.count
let range = NSMakeRange(startIndex, endIndex)
ranges.append(range)
}
position += (word.count + 1)
}
}
return ranges
}
func setColorToChar(_ chars: [String] , color: [UIColor]) -> NSMutableAttributedString {
let attributedString = NSMutableAttributedString(string: self)
if chars.count != color.count {
fatalError("Colors are not added correctly")
}
// let ranges = getRanges(of: char)
for i in 0..<chars.count {
let ranges = getRanges(of: chars[i])
for range in ranges {
attributedString.addAttributes([NSForegroundColorAttributeName: color[i]], range: range)
}
}
return attributedString
}
}
class ViewController: UIViewController,UITextViewDelegate {
#IBOutlet weak var myTextView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
myTextView.delegate = self
}
func textViewDidChange(_ textView: UITextView) {
textView.attributedText = textView.text.setColorToChar(["//","let"], color: [.red,.green])
}
}
Result screenshot
You can do this easily by making extension for UITextView
extension UITextView {
func halfTextColorChange (fullText : String , changeText : String ) {
let strNumber: NSString = fullText as NSString
let range = (strNumber).range(of: changeText)
let attribute = NSMutableAttributedString.init(string: fullText)
attribute.addAttribute(NSAttributedStringKey.foregroundColor, value: UIColor(red: 76/255, green: 76/255, blue:76/255, alpha: 1.0) , range: range)
self.attributedText = attribute
}
}
Usage
txtView.halfTextColorChange(fullText: "ABCXYZ", changeText: "XYZ")

How to add a grouping separator while user is typing a number into UITextField?

I am doing a currency type UITextField. The behavior I need is like this:
I managed to create the decimal part. But I have problems adding in the grouping separators for thousands. How do I group the integer part here?
Here is the code so far:
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var textField: UITextField!
let DIGITS = ["0","1","2","3","4","5","6","7","8","9","0"]
let DECIMAL_SEPERATOR = ","
let THOUSAND_SEPERATOR = "."
let DECIMAL_DIGITS = 2
let DECIMAL_DIGITS_DEFAULT_STRING = "00"
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = self
textField.text = "0\(DECIMAL_SEPERATOR)\(DECIMAL_DIGITS_DEFAULT_STRING)"
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//Textfield
func textFieldTriggerDone(){
if !textField.text!.containsString(DECIMAL_SEPERATOR){
textField.text = textField.text! + DECIMAL_SEPERATOR + DECIMAL_DIGITS_DEFAULT_STRING
}
print("Add missing parts here")
}
func textFieldTriggerDecimalTyped(){
textField.text = textField.text! + DECIMAL_SEPERATOR
}
func textFieldShouldAddNumber(text: String, range: NSRange, replacement: String, dots: Int) -> Bool{
let nsstring = NSString(string: text)
let decimalRange = nsstring.rangeOfString(DECIMAL_SEPERATOR)
if range.location > decimalRange.location{
let parts = text.componentsSeparatedByString(DECIMAL_SEPERATOR)
if parts.count > 1{
if parts[1].characters.count > (DECIMAL_DIGITS-1){
return false
}
}
}else if range.location < decimalRange.location{
if textField.text!.characters.count == 1 && textField.text! == "0"{
textField.text = replacement
return false
}else{
let insertIndex = text.startIndex.advancedBy(range.location-dots)
var finalText = text
if replacement.characters.count > 0 {
finalText.insert(replacement.characters.first!, atIndex: insertIndex)
textField.text = finalText
let begin = textField.beginningOfDocument
let pos = textField.positionFromPosition(begin, offset: (range.location+1))
let cursorpos = textField.textRangeFromPosition(pos!, toPosition: pos!)
textField.selectedTextRange = cursorpos
//textFieldAddThousandSeperators()
return false
}
}
}
return true
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
func textFieldDidEndEditing(textField: UITextField) {
textFieldTriggerDone()
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let dots = textField.text!.occurancesOf(THOUSAND_SEPERATOR)
if string == ""{
if textField.text!.characters.count > 1{
return true
}else{
textField.text! = "0"
return false
}
}
if range.location == 0 && string == "0"{
return false
}
if DIGITS.contains(string){
return textFieldShouldAddNumber(textField.text!, range: range, replacement: string, dots: dots)
}
if string == DECIMAL_SEPERATOR || string == THOUSAND_SEPERATOR{
if textField.text!.containsString(DECIMAL_SEPERATOR){
return false
}
textFieldTriggerDecimalTyped()
return false
}
return false
}
}
Check out this answer:
https://stackoverflow.com/a/40469426/6863743
code:
override func viewDidLoad() {
super.viewDidLoad()
textField.addTarget(self, action: #selector(myTextFieldDidChange), for: .editingChanged)
}
func myTextFieldDidChange(_ textField: UITextField) {
if let amountString = textField.text?.currencyInputFormatting() {
textField.text = amountString
}
}
extension String {
// formatting text for currency textField
func currencyInputFormatting() -> String {
var number: NSNumber!
let formatter = NumberFormatter()
formatter.numberStyle = .currencyAccounting
formatter.currencySymbol = "$"
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
var amountWithPrefix = self
// remove from String: "$", ".", ","
let regex = try! NSRegularExpression(pattern: "[^0-9]", options: .caseInsensitive)
amountWithPrefix = regex.stringByReplacingMatches(in: amountWithPrefix, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, self.characters.count), withTemplate: "")
let double = (amountWithPrefix as NSString).doubleValue
number = NSNumber(value: (double / 100))
// if first number is 0 or all numbers were deleted
guard number != 0 as NSNumber else {
return ""
}
return formatter.string(from: number)!
}
}
To get it to convert back to something you can do calculations with, you can just write a function to take out the "," and "." and so on.
Hope this helps a bit, it's the best solution I've found for currency formatting.

Swift 3 - how do I add clickable links to another view controller in the body of a TextView? [duplicate]

I am trying to display an attributed string in a UITextview with clickable links. I've created a simple test project to see where I'm going wrong and still can't figure it out. I've tried enabling user interaction and setting the shouldInteractWithURLs delegate method, but it's still not working. Here's my code (for a view controller that only contains a textview)
#IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let string = "Google"
let linkString = NSMutableAttributedString(string: string)
linkString.addAttribute(NSLinkAttributeName, value: NSURL(string: "https://www.google.com")!, range: NSMakeRange(0, string.characters.count))
linkString.addAttribute(NSFontAttributeName, value: UIFont(name: "HelveticaNeue", size: 25.0)!, range: NSMakeRange(0, string.characters.count))
textView.attributedText = linkString
textView.delegate = self
textView.selectable = true
textView.userInteractionEnabled = true
}
And here are the delegate methods I've implemented:
func textViewShouldBeginEditing(textView: UITextView) -> Bool {
return false
}
func textView(textView: UITextView, shouldInteractWithURL URL: NSURL, inRange characterRange: NSRange) -> Bool {
return true
}
This still isn't working. I've searched on this topic and nothing has helped yet. Thanks so much in advance.
Just select the UITextView in your storyboard and go to "Show Attributes inspector" and select selectable and links. As the image below shows. Make sure Editable is unchecked.
For swift3.0
override func viewDidLoad() {
super.viewDidLoad()
let linkAttributes = [
NSLinkAttributeName: NSURL(string: "http://stalwartitsolution.co.in/luminutri_flow/terms-condition")!
] as [String : Any]
let attributedString = NSMutableAttributedString(string: "Please tick box to confirm you agree to our Terms & Conditions, Privacy Policy, Disclaimer. ")
attributedString.setAttributes(linkAttributes, range: NSMakeRange(44, 18))
attributedString.addAttribute(NSUnderlineStyleAttributeName, value: NSNumber(value: 1), range: NSMakeRange(44, 18))
textview.delegate = self
textview.attributedText = attributedString
textview.linkTextAttributes = [NSForegroundColorAttributeName: UIColor.red]
textview.textColor = UIColor.white
}
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
return true
}
Swift 3 iOS 10: Here's Clickable extended UITextView that detect websites inside the textview automatically as long as the link start with www. for example: www.exmaple.com if it exist anywhere in the text will be clickable. Here's the class:
import Foundation
import UIKit
public class ClickableTextView:UITextView{
var tap:UITapGestureRecognizer!
override public init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
print("init")
setup()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup(){
// Add tap gesture recognizer to Text View
tap = UITapGestureRecognizer(target: self, action: #selector(self.myMethodToHandleTap(sender:)))
// tap.delegate = self
self.addGestureRecognizer(tap)
}
func myMethodToHandleTap(sender: UITapGestureRecognizer){
let myTextView = sender.view as! UITextView
let layoutManager = myTextView.layoutManager
// location of tap in myTextView coordinates and taking the inset into account
var location = sender.location(in: myTextView)
location.x -= myTextView.textContainerInset.left;
location.y -= myTextView.textContainerInset.top;
// character index at tap location
let characterIndex = layoutManager.characterIndex(for: location, in: myTextView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
// if index is valid then do something.
if characterIndex < myTextView.textStorage.length {
let orgString = myTextView.attributedText.string
//Find the WWW
var didFind = false
var count:Int = characterIndex
while(count > 2 && didFind == false){
let myRange = NSRange(location: count-1, length: 2)
let substring = (orgString as NSString).substring(with: myRange)
// print(substring,count)
if substring == " w" || (substring == "w." && count == 3){
didFind = true
// print("Did find",count)
var count2 = count
while(count2 < orgString.characters.count){
let myRange = NSRange(location: count2 - 1, length: 2)
let substring = (orgString as NSString).substring(with: myRange)
// print("Did 2",count2,substring)
count2 += 1
//If it was at the end of textView
if count2 == orgString.characters.count {
let length = orgString.characters.count - count
let myRange = NSRange(location: count, length: length)
let substring = (orgString as NSString).substring(with: myRange)
openLink(link: substring)
print("It's a Link",substring)
return
}
//If it's in the middle
if substring.hasSuffix(" "){
let length = count2 - count
let myRange = NSRange(location: count, length: length)
let substring = (orgString as NSString).substring(with: myRange)
openLink(link: substring)
print("It's a Link",substring)
return
}
}
return
}
if substring.hasPrefix(" "){
print("Not a link")
return
}
count -= 1
}
}
}
func openLink(link:String){
if let checkURL = URL(string: "http://\(link.replacingOccurrences(of: " ", with: ""))") {
if UIApplication.shared.canOpenURL(checkURL) {
UIApplication.shared.open(checkURL, options: [:], completionHandler: nil)
print("url successfully opened")
}
} else {
print("invalid url")
}
}
public override func didMoveToWindow() {
if self.window == nil{
self.removeGestureRecognizer(tap)
print("ClickableTextView View removed from")
}
}
}

NSAttributedString get images and string in parts

I have an NSAttributedString with a mixture of String and NSTextAttachment with images in there. How would I extract an [AnyObject] array of the parts?
This worked for me in Swift 4:
extension UITextView {
func getParts() -> [AnyObject] {
var parts = [AnyObject]()
let attributedString = self.attributedText
let range = NSMakeRange(0, attributedString.length)
attributedString.enumerateAttributes(in: range, options: NSAttributedString.EnumerationOptions(rawValue: 0)) { (object, range, stop) in
if object.keys.contains(NSAttributedStringKey.attachment) {
if let attachment = object[NSAttributedStringKey.attachment] as? NSTextAttachment {
if let image = attachment.image {
parts.append(image)
} else if let image = attachment.image(forBounds: attachment.bounds, textContainer: nil, characterIndex: range.location) {
parts.append(image)
}
}
} else {
let stringValue : String = attributedString.attributedSubstring(from: range).string
if (!stringValue.trimmingCharacters(in: .whitespaces).isEmpty) {
parts.append(stringValue as AnyObject)
}
}
}
return parts
}
I figured it out you can iterate over all the attributedString and read if the object has an NSTextAttachmentAttributeName property. If not, assume it's a string.
extension UITextView {
func getParts() -> [AnyObject] {
var parts = [AnyObject]()
let attributedString = self.attributedText
let range = NSMakeRange(0, attributedString.length)
attributedString.enumerateAttributesInRange(range, options: NSAttributedStringEnumerationOptions(rawValue: 0)) { (object, range, stop) in
if object.keys.contains(NSAttachmentAttributeName) {
if let attachment = object[NSAttachmentAttributeName] as? NSTextAttachment {
if let image = attachment.image {
parts.append(image)
}else if let image = attachment.imageForBounds(attachment.bounds, textContainer: nil, characterIndex: range.location) {
parts.append(image)
}
}
}else {
let stringValue : String = attributedString.attributedSubstringFromRange(range).string
if !stringValue.isEmptyOrWhitespace() {
parts.append(stringValue)
}
}
}
return parts
}
}

function to find all NSRanges for a string in string in swift

I would like to have all CG letters pairs in red color in DNA sequences (string). I can do it by using NSMutableAttributedString.
For this I need NSRanges of all CG positions and I have tried to make the function below but in the line
indexesOfStringIn = (stringOut as NSString).rangeOfString(stringIn, options: 0, range: rangeForIndexStringIn)
rangeOfString with range cannot be invoked with an argument list of type (String, options: Int, range: NSRange). Where is a mistake ? What i did wrong ?
func searchForNSRangesOfStringInString(stringOut: String, stringIn: String) -> [NSRange]
{
var arrayOfindexesOfStringIn = [NSRange]()
if stringIn.characters.count > 1
{
var rangeNS = (stringOut as NSString).rangeOfString(stringIn)
if rangeNS.location != NSNotFound
{
let endIndexOfStringOut = (stringOut as NSString).length - 1
var indexesOfStringIn = (stringOut as NSString).rangeOfString(stringIn)
arrayOfindexesOfStringIn = [indexesOfStringIn]
var lastIndexOfStringIn = (stringOut as NSString).rangeOfString(stringIn).location
var rangeForIndexStringIn = NSRange(lastIndexOfStringIn...endIndexOfStringOut)
while indexesOfStringIn.location != NSNotFound
{
indexesOfStringIn = (stringOut as NSString).rangeOfString(stringIn, options: 0, range: rangeForIndexStringIn)
if indexesOfStringIn.location != NSNotFound
{
arrayOfindexesOfStringIn.append(indexesOfStringIn)
lastIndexOfStringIn = (stringOut as NSString).rangeOfString(stringIn, options: 0, range: rangeForIndexStringIn).location
rangeForIndexStringIn = NSRange(lastIndexOfStringIn...endIndexOfStringOut)
}
}
}
}
return arrayOfindexesOfStringIn
}
As suggested by uchuugaka it is much easier to use NSRegularExpression to return an array of the matches ranges in a string as follow:
extension String {
func findOccurencesOf(text text:String) -> [NSRange] {
return !text.isEmpty ? try! NSRegularExpression(pattern: text, options: []).matchesInString(self, options: [], range: NSRange(0..<characters.count)).map{ $0.range } : []
}
}
let str = "CGCGCGCGCG"
let ranges = str.findOccurencesOf(text: "CG")
print(ranges.count) // 5
Just add a control event for your textField Editing changed and loop through the ranges as follow:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
textField.addTarget(self, action: "coloring:", forControlEvents: UIControlEvents.EditingChanged)
textField.becomeFirstResponder()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func coloring(sender:UITextField) {
let attText = NSMutableAttributedString(string: sender.text ?? "")
let ranges = sender.text!.uppercaseString.findOccurencesOf(text: "CG")
for range in ranges {
attText.addAttributes([NSForegroundColorAttributeName : UIColor.redColor()], range: range)
}
sender.attributedText = attText
}
}
extension String {
func findOccurencesOf(text text:String) -> [NSRange] {
return !text.isEmpty ? try! NSRegularExpression(pattern: text, options: []).matchesInString(self, options: [], range: NSRange(0..<characters.count)).map{ $0.range } : []
}
}