I’m using the below code to get the cursor position when the user types something in the textView.
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
inputChar = text
inputRange = range
inputIndex = inputRange?.upperBound
self.presenter.hashtagDataArray.removeAll()
if(text == "\n") {
textView.resignFirstResponder()
toggleTableView(toggle: true)
return false
}
let str = (textView.text + text)
if str.utf16.count <= MediaPostViewController.descCharacterLimits {
return true
}
let numberOfChars = str.utf16.count
lbbbl_DescCount.text = "\(textView.text.utf16.count)/\(MediaPostViewController.descCharacterLimits)"
return (numberOfChars <= MediaPostViewController.descCharacterLimits) || (str.utf16.count < textView.text.utf16.count)
}
func textViewDidChange(_ textView: UITextView) {
var indexPosition : Int?
updateCharacterCount()
self.mainScrollView.isScrollEnabled = false
if textView.text != "" {
inputIndex = (String(textView.text.utf16) as NSString?)?.substring(with: NSRange(location: 0, length: inputRange!.location)).count
cursorPosition = inputIndex! + 1
inputText = textView.text!
textViewEndIndex = textView.text.unicodeScalars.endIndex.utf16Offset(in: textView.text)
if #available(iOS 10.2, *) {
inputText = textView.text.replaceEmoji(with: "@")
} else {
//Fallback on earlier versions
}
if inputChar == "" || inputChar == " "{
indexPosition = cursorPosition!-2
}
else {
indexPosition = cursorPosition!-1
}
guard let enteredText = inputText?.utf16.subString(from: 0, to: indexPosition!) else { return }
guard let lastdelimiterposition = enteredText.lastIndexPosition(of: "#") else { return }
hashwordstartIndex = lastdelimiterposition
checkhashinword = inputText?.utf16.subString(from: lastdelimiterposition, to: indexPosition!)
if inputChar == "" || inputChar == " "{
spaceCharactersCheck = 1
}
if spaceCharactersCheck == 1{
checkhashword = checkhashinword?.components(separatedBy: " ").filter({!$0.contains("#")}).joined(separator: " ")
checkhashedword = checkhashinword?.components(separatedBy: " ").filter({$0.contains("#")}).joined(separator: " ")
}
if let checkhashinword = checkhashinword {
if checkhashinword.utf16.count > 1 && !(checkhashinword.contains(" ")){
self.presenter.returnHashTagsData((checkhashinword.utf16.subString(from: 1, to: checkhashinword.utf16.count-1))!)
}
}
}
else {
self.toggleTableView(toggle: true)
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let rowSelected = tableView.cellForRow(at: indexPath)?.textLabel?.text
var textViewValue : String?
textViewValue = textView_Desc.text!
let startIndex = String.Index(utf16Offset: hashwordstartIndex!, in: String(textViewValue!))
let hashwordendIndex = String.Index(utf16Offset: inputIndex!, in: String(textViewValue!))
let range = startIndex...hashwordendIndex
if var strNewText = textViewValue?.components(separatedBy: "#") {
if strNewText.count > 1 {
if let textlabel = rowSelected {
strNewText[strNewText.count - 1] = textlabel
}
}
if var rowSelected = rowSelected {
if let checkhashword = checkhashword
{
if checkhashword != ""
{
rowSelected = rowSelected.appending(" ").appending(checkhashword).appending(String(checkhashedword!))
}
}
if textViewValue != ""{
if textViewEndIndex != inputIndex {
do {
if let result = textViewValue?.replaceSubrange(range, with: rowSelected)
{
print("result:\(result)")
}
else {
throw RangeException.notaValidRange
}
}
catch {
}
}
}
spaceCharactersCheck = 0
checkhashword = ""
checkhashedword = ""
}
let combinedText = strNewText.joined(separator: "#")//.appending(" ")
if combinedText.count-1 > MediaPostViewController.descCharacterLimits {
if textView_Desc.text.count-1 > MediaPostViewController.descCharacterLimits {
toggleTableView(toggle: true)
return
}
}
if textViewEndIndex != inputIndex {
textView_Desc.text = textViewValue
}
else {
textView_Desc.text = combinedText.replacingOccurrences(of: "##", with: "#")
}
lbbbl_DescCount.text = "\(textView_Desc.text.count)/\(MediaPostViewController.descCharacterLimits)"
}
toggleTableView(toggle: true)
self.hideScrollView.isHidden = false
self.view.bringSubviewToFront(hideScrollView)
}
}
Use case : Trying to implement hashtags similar to Instagram.
Approach:
There is a textView and I’ve added a tableView beneath that. The tableView gets data from API call based on the user input in textView. For instance, if user types #a then I show the tableView and tableView is loaded with suggestions like (#abc,#abcd, etc) from API call. The user can select a row and after selection, I hide the tableView. It works perfectly fine when the user enters hashtags between a text like #abc #insta and say if the user tries to type #ba between this #abc #insta it gets perfectly inserted after user chooses a suggestion from tableView (like #abc#bat#insta)
Problem:
When I have emojis the text replaces the emoji. For eg: if the user enters emoi#a then it will get a list starting with #a (like #abc,#ab etc) and now if the user selects #abc then it populates the textView like #abc#a and emoji disappears.
I don’t see a solution to this problem in any Github repositories.
Has anyone faced a similar problem?