Long time user, first time poster, so my apologies if I make any errors in presenting my question. I have been working on this for hours and I've decided it is time to ask the experts. I have also searched through every similar question that has been "answered" and work, which leads me to believe they are outdated.
I am attempting to grab the tapped word from a UITextview that would be used later in the code. For example, there is a paragraph of words in the text view:
"The initial return on time investment is much smaller, due to him trading his upfront cost for sweat-equity in the company, but the potential long-term payout is much greater".
I would want to be able to tap on a word, e.g. 'investment', and run it through another function to define it. However simply tapping the word, crashes the program, and I do not receive the word tapped.
I implemented a tap gesture recognizer:
let tap = UITapGestureRecognizer(target: self, action: #selector(tapResponse(_:)))
tap.delegate = self
tvEditor.addGestureRecognizer(tap)
and then wrote the function: 2
func tapResponse(recognizer: UITapGestureRecognizer) {
let location: CGPoint = recognizer.locationInView(tvEditor)
let position: CGPoint = CGPointMake(location.x, location.y)
let tapPosition: UITextPosition = tvEditor.closestPositionToPoint(position)!
let textRange: UITextRange = tvEditor.tokenizer.rangeEnclosingPosition(tapPosition, withGranularity: UITextGranularity.Word, inDirection: 1)!
let tappedWord: String = tvEditor.textInRange(textRange)!
print("tapped word : %@", tappedWord)
}
Ideally, this should take the location from the tapped part of the Textview, take the position by taking the .x & .y, and then looking through the Textview at the point closest to the position, finding the Range enclosing the position with granularity (to return the word), and setting the contents as a String, which I am currently just printing to the console. However, on tapping the word, I receive this crash.3
along with "fatal error: unexpectedly found nil while unwrapping an Optional value" in the console.
Any help would be greatly appreciated. I may just be missing something simple, or it could be much more complicated.
Swift 3.0 Answer - Working as of July 1st, 2016
In my ViewDidLoad() -
I use text from a previous VC, so my variable "theText" is already declared. I included a sample string that has been noted out.
//Create a variable of the text you wish to attribute.
let textToAttribute = theText // or "This is sample text"
// Break your string in to an array, to loop through it.
let textToAttributeArray = textToAttribute.componentsSeparatedByString(" ")
// Define a variable as an NSMutableAttributedString() so you can append to it in your loop.
let attributedText = NSMutableAttributedString()
// Create a For - In loop that goes through each word your wish to attribute.
for word in textToAttributeArray{
// Create a pending attribution variable. Add a space for linking back together so that it doesn't looklikethis.
let attributePending = NSMutableAttributedString(string: word + " ")
// Set an attribute on part of the string, with a length of the word.
let myRange = NSRange(location: 0, length: word.characters.count)
// Create a custom attribute to get the value of the word tapped
let myCustomAttribute = [ "Tapped Word:": word]
// Add the attribute to your pending attribute variable
attributePending.addAttributes(myCustomAttribute, range: myRange)
print(word)
print(attributePending)
//append 'attributePending' to your attributedText variable.
attributedText.appendAttributedString(attributePending) ///////
print(attributedText)
}
textView.attributedText = attributedText // Add your attributed text to textview.
Now we will add a tap gesture recognizer to register taps.
let tap = UITapGestureRecognizer(target: self, action: #selector(HandleTap(_:)))
tap.delegate = self
textView.addGestureRecognizer(tap) // add gesture recognizer to text view.
Now we declare a function under the viewDidLoad()
func HandleTap(sender: UITapGestureRecognizer) {
let myTextView = sender.view as! UITextView //sender is TextView
let layoutManager = myTextView.layoutManager //Set layout manager
// location of tap in myTextView coordinates
var location = sender.locationInView(myTextView)
location.x -= myTextView.textContainerInset.left;
location.y -= myTextView.textContainerInset.top;
// character index at tap location
let characterIndex = layoutManager.characterIndexForPoint(location, inTextContainer: myTextView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
// if index is valid then do something.
if characterIndex < myTextView.textStorage.length {
// print the character index
print("Your character is at index: \(characterIndex)") //optional character index.
// print the character at the index
let myRange = NSRange(location: characterIndex, length: 1)
let substring = (myTextView.attributedText.string as NSString).substringWithRange(myRange)
print("character at index: \(substring)")
// check if the tap location has a certain attribute
let attributeName = "Tapped Word:" //make sure this matches the name in viewDidLoad()
let attributeValue = myTextView.attributedText.attribute(attributeName, atIndex: characterIndex, effectiveRange: nil) as? String
if let value = attributeValue {
print("You tapped on \(attributeName) and the value is: \(value)")
}
}
}