How to get a sequence of words form uitextview - ios

I am able to get the word on tapping uitextview but I want a sequence of words by tapping one of it. Image
Over here in the image if I click on Australia I can get the name but for New Zealand I am only getting new or zealand. Is there any way I can get New Zealand as a whole word. My code is.
countryTxtView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(didReceiveGestureOnText(recognizer:))))
#objc func didReceiveGestureOnText(recognizer: UITapGestureRecognizer) {
guard let textView = recognizer.view as? UITextView else {return}
let point = recognizer.location(in: textView)
guard let detectedWord = getWordAtPosition(point, textView: textView) else {return} print("selected word : (detectedWord)")
}
func getWordAtPosition(_ point: CGPoint, textView: UITextView) -> String?{
if let textPosition = textView.closestPosition(to: point){
if let range = textView.tokenizer.rangeEnclosingPosition(textPosition, with: .sentence, inDirection: UITextDirection(rawValue: 1)){
return textView.text(in: range)
}
}
return nil
}

Related

Get NSAttributed String color on tap SWIFT

I'm using a UITextView which contains multiple colors. I'm using NSMutableAttributedString for this. I'm fetching the words by tapping on the word inside the TextView in console. Now I want to fetch the color of the attributed string along with the word. I've found no resource in Swift. Everything I found was in Objective-C. Here is my UITapGestureRecognizer handler function.
#objc func myMethodToHandleTap(sender: UITapGestureRecognizer) {
let textView = sender.view as! UITextView
let location: CGPoint = sender.location(in: textView)
let position: CGPoint = CGPoint(x: location.x, y: location.y)
let tapPosition: UITextPosition? = textView.closestPosition(to: position)
if tapPosition != nil {
let textRange: UITextRange? = textView.tokenizer.rangeEnclosingPosition(tapPosition!, with: UITextGranularity.word, inDirection: UITextDirection(rawValue: 1))
if textRange != nil && textColor == UIColor.red
{
let tappedWord: String? = textView.text(in: textRange!)
print("Color : RED , tapped word : ", tappedWord!)
}
else if textRange != nil && textColor == UIColor.green
{
let tappedWord: String? = textView.text(in: textRange!)
print("Color : GREEN , tapped word : ", tappedWord!)
}
else {
print("empty space")
}
}
}
If you adding word color programmatic than check tap word range and match with you applied range then you can easily find the color of the tapped word.
textColor where you assign a value before compare?
This will help you
Thank you.
You can get the color this way:
if
let tapPosition = tapPosition,
let characterIndex = textView.offset(from: textView.beginningOfDocument, to: tapPosition)
let textColor = textView.attributedText.attribute(.foregroundColor, at: characterIndex, effectiveRange: nil) as? UIColor
{
//Do what you want with text color
}

How to get the Character that is before the cursor in a UITextView?

Let's say I have the following UITextView object:
var textView = UITextView()
textView.text = "Hello World!"
Now let's say I don't want to allow the user to delete the "W" character while editing it. How could I know which character is before the cursor (or selected by it)?
I'm looking for something that would work like this:
if textView.characterBeforeCursor() != "W" {
textView.deleteBackward()
}
or... (when the user selects the "W" character):
if textView.selectedTextContains("W") == false {
textView.deleteBackward()
}
What approach should I use to accomplish this?
Here's an idea, not fully tested, but seems to work... Just grab the character about to be acted upon and block backspace if its the target... Also with regard to selection of text, if the selection contains the target at all, we block new text.
import UIKit
class ViewController: UIViewController, UITextViewDelegate {
#IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
self.textView.delegate = self
// Do any additional setup after loading the view, typically from a nib.
}
func characterBeforeCursor() -> String? {
// get the cursor position
if let cursorRange = textView.selectedTextRange {
// get the position one character before the cursor start position
if let newPosition = textView.position(from: cursorRange.start, offset: -1) {
let range = textView.textRange(from: newPosition, to: cursorRange.start)
return textView.text(in: range!)
}
}
return nil
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if (characterBeforeCursor() == "W") {
let char = text.cString(using: String.Encoding.utf8)!
let isBackSpace = strcmp(char, "\\b")
if (isBackSpace == -92) {
return false
}
return true
}
else {
if let range = textView.selectedTextRange {
let selectedText = textView.text(in: range)
if (selectedText!.contains("W")) {
return false
}
}
return true
}
}
}
This should do it:
let forbiddenLetter = "W"
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
guard let txt = textView.text, let txtRange = Range(range, in: txt) else {
return false
}
let subString: Substring = txt[txtRange]
return !subString.contains(forbiddenLetter)
}
In the code above let txt = textView.text is just for simplicity, we could keep force-unwrapping textView.text! since the .text property is designed never returns nil for a non-nil UITextView.
By let txtRange = Range(range, in: txt) we get a variable of type Range<String.Index> instead of the vanilla NSRange that range is. By doing so we can get the Substring of txt that the textView is about to change.
Finally, the result of checking whether or not the subString contains the forbiddenLetter, is returned.
This snippet would prevent deleting W by using:
Backspace key ⌫
Deleting selection
Pasting over selection
Autocorrect (from the popup)

Get tapped word in a UITextview

I have added a uitextview which is initially non editable. I added a tap gesture which enable the editing to true. In the tap gesture selector I get the word that is being tapped. I have tried a lot many solution but none worked for me as a complete solution. Every solution worked if the textview is not scrolled. But if I scroll the textview the exact word is not retrieved. Here is my code for getting the tapped word:
#objc func handleTap(_ sender: UITapGestureRecognizer) {
notesTextView.isEditable = true
notesTextView.textColor = UIColor.white
if let textView = sender.view as? UITextView {
var pointOfTap = sender.location(in: textView)
print("x:\(pointOfTap.x) , y:\(pointOfTap.y)")
let contentOffsetY = textView.contentOffset.y
pointOfTap.y += contentOffsetY
print("x:\(pointOfTap.x) , y:\(pointOfTap.y)")
word(atPosition: pointOfTap)
}
func word(atPosition: CGPoint) -> String? {
if let tapPosition = notesTextView.closestPosition(to: atPosition) {
if let textRange = notesTextView.tokenizer.rangeEnclosingPosition(tapPosition , with: .word, inDirection: 1) {
let tappedWord = notesTextView.text(in: textRange)
print("Word: \(tappedWord)" ?? "")
return tappedWord
}
return nil
}
return nil
}
EDITED:
Here is the demo project with the problem.
https://github.com/amrit42087/TextViewDemo
The best and easiest way in Swift 4
METHOD 1:
Step 1: Add Tap Gesture on the textview
let tap = UITapGestureRecognizer(target: self, action: #selector(tapResponse(recognizer:)))
textViewTC.addGestureRecognizer(tap)
Step 2: Implement Tap Gesture
#objc func tapResponse(recognizer: UITapGestureRecognizer) {
let location: CGPoint = recognizer.location(in: textViewTC)
let position: CGPoint = CGPoint(x: location.x, y: location.y)
let tapPosition: UITextPosition = textViewTC.closestPosition(to: position)!
guard let textRange: UITextRange = textViewTC.tokenizer.rangeEnclosingPosition(tapPosition, with: UITextGranularity.word, inDirection: 1) else {return}
let tappedWord: String = textViewTC.text(in: textRange) ?? ""
print("tapped word ->", tappedWord)
}
And yes thats it. Go for it.
METHOD 2:
The alternate way is that you can enable links for textview and then set the same as an attribute. Here is an example
var foundRange = attributedString.mutableString.range(of: "Terms of Use") //mention the parts of the attributed text you want to tap and get an custom action
attributedString.addAttribute(NSAttributedStringKey.link, value: termsAndConditionsURL, range: foundRange)
set this attribute text to Textview and textView.delegate = self
Now you just need to handle the response in
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
Hope it helps you. All the best.
You don't need to add the content offset of the text view. When you convert location into a scrollview it will already take its content offset into account.
Removing:
let contentOffsetY = textView.contentOffset.y
pointOfTap.y += contentOffsetY
should work.
Please Try This
//add UITextViewDelegate
let termsAndConditionsURL = "someText"
let privacyURL = "SampleText"
#IBOutlet weak var terms: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
self.terms.delegate = self
// Adding Attributed text to TextView
let str = "By registering, you agree to the Terms and the User Privacy Statement."
let attributedString = NSMutableAttributedString(string: str)
var foundRange = attributedString.mutableString.range(of: "Terms")
attributedString.addAttribute(NSAttributedStringKey.foregroundColor, value: UIColor.blue, range: foundRange)
attributedString.addAttribute(NSAttributedStringKey.underlineStyle , value: NSUnderlineStyle.styleSingle.rawValue, range: foundRange)
attributedString.addAttribute(NSAttributedStringKey.link, value: termsAndConditionsURL, range: foundRange)
foundRange = attributedString.mutableString.range(of: "User Privacy Statement")
attributedString.addAttribute(NSAttributedStringKey.foregroundColor, value: UIColor.blue, range: foundRange)
attributedString.addAttribute(NSAttributedStringKey.underlineStyle , value: NSUnderlineStyle.styleSingle.rawValue, range: foundRange)
attributedString.addAttribute(NSAttributedStringKey.link, value: privacyURL, range: foundRange)
terms.attributedText = attributedString
// Do any additional setup after loading the view.
}
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool
{
if (URL.absoluteString == termsAndConditionsURL)
{
// Perform Terms Action here
} else if (URL.absoluteString == privacyURL)
{
// Perform Terms Action here
}
return false
}

Get selected text in a UITextView

I have a UITextView, and I want to allow the user to highlight a portion of text and copy it with a button instead of using the default Apple method. The problem is that I can't get the text within the selected range.
Here's what I have:
#IBAction func copyButton(_ sender: Any) {
let selectedRange: UITextRange? = textView.selectedTextRange
selectedText = textView.textInRange(selectedRange)
UIPasteboard.general.string = selectedText
}
But I'm getting
UITextView has no member textInRange
and I'm not sure what I should be using instead.
What is happening is that UITextView method textInRange have been renamed to text(in: Range) since Swift 3. Btw you forgot to add the let keyword in your sentence:
if let range = textView.selectedTextRange {
UIPasteboard.general.string = textView.text(in: range)
}

How to display clickable links in UITextView

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")
}
}
}

Resources