123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470 |
- //
- // OEMentions.swift
- // OEMentions
- //
- // Created by Omar Alessa on 7/31/16.
- // Copyright © 2016 omaressa. All rights reserved.
- //
- import UIKit
- protocol OEMentionsDelegate
- {
- // To respond to the selected name
- func mentionSelected(id:Int, name:String)
- func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath, oeObject: OEObject) -> UITableViewCell
- func tableViewPositionUpdated()
- func textViewShouldUpdateTextColor()
- }
- class OEMentions: NSObject, UITextViewDelegate, UITableViewDelegate, UITableViewDataSource {
-
- // UIViewController view
- var mainView:UIView?
-
- // UIView for the textview container
- var containerView:UIView?
-
- // The UITextView we want to add mention to
- var textView:UITextView?
-
- // List of names to show in the list
- private var oeObjects:[OEObject]?
-
- // [Index:Length] of added mentions to textview
- var mentionsIndexes = [Int:[String: Any]]()
-
- // Keep track if still searching for a name
- var isMentioning = Bool()
-
- // The search query
- var mentionQuery = String()
-
- // The start of mention index
- var startMentionIndex = Int()
-
- // Character that show the mention list (Default is "@"), It can be changed using changeMentionCharacter func
- private var mentionCharater = "@"
-
- // Keyboard hieght after it shows
- var keyboardHieght:CGFloat?
-
-
- // Mentions tableview
- var tableView: UITableView!
-
- //MARK: Customizable mention list properties
-
- // Color of the mention tableview name text
- var nameColor = UIColor.blue
-
- // Font of the mention tableview name text
- var nameFont = UIFont.boldSystemFont(ofSize: 14.0)
-
- // Color if the rest of the UITextView text
- var notMentionColor = UIColor.black
-
- // OEMention table view full in container view
- var showMentionFullInContainer:Bool = true
-
- private var filteredOEObjects: [OEObject]?
-
-
- // OEMention Delegate
- var delegate:OEMentionsDelegate?
-
- var textViewWidth:CGFloat?
- var textViewHeight:CGFloat?
- var textViewYPosition:CGFloat?
-
- var containerHieght:CGFloat?
-
- //MARK: class init without container
- init(textView:UITextView, mainView:UIView, oeObjects:[OEObject]){
- super.init()
-
- self.mainView = mainView
- self.setOeObjects(oeObjects: oeObjects)
- self.textView = textView
-
- self.textViewWidth = textView.frame.width
-
- initMentionsList()
-
- NotificationCenter.default.addObserver(self, selector: #selector(OEMentions.keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
- }
-
- //MARK: class init with container
- init(containerView:UIView, textView:UITextView, mainView:UIView, oeObjects:[OEObject]){
- super.init()
-
- self.containerView = containerView
- self.mainView = mainView
- self.setOeObjects(oeObjects: oeObjects)
- self.textView = textView
-
- self.containerHieght = containerView.frame.height
-
- self.textViewWidth = textView.frame.width
- self.textViewHeight = textView.frame.height
- self.textViewYPosition = textView.frame.origin.y
-
- initMentionsList()
-
- NotificationCenter.default.addObserver(self, selector: #selector(OEMentions.keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
- }
-
- func setOeObjects(oeObjects: [OEObject]?) {
- self.oeObjects = oeObjects
- self.filteredOEObjects = oeObjects
- }
-
-
- // Set the mention character. Should be one character only, default is "@"
- func changeMentionCharacter(character: String){
- if character.count == 1 && character != " " {
- self.mentionCharater = character
- }
- }
-
- // Change tableview background color
- func changeMentionTableviewBackground(color: UIColor){
- self.tableView.backgroundColor = color
- }
-
- func changeMentionTableviewSeparatorColor(color: UIColor) {
- self.tableView.separatorColor = color
- }
-
-
- //MARK: UITextView delegate functions:
-
- func textViewDidEndEditing(_ textView: UITextView) {
-
- self.mentionQuery = ""
- self.isMentioning = false
- UIView.animate(withDuration: 0.2, animations: {
- self.tableView.isHidden = true
- })
-
- }
-
- func textViewDidChange(_ textView: UITextView) {
-
- self.textView!.isScrollEnabled = false
- self.textView!.sizeToFit()
- self.textView!.frame.size.width = textViewWidth!
-
- updatePosition()
- }
-
- func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
-
- let str = String(textView.text)
- var lastCharacter = "nothing"
-
- if !str.isEmpty && range.location != 0{
- lastCharacter = String(str[str.index(before: str.endIndex)])
- }
-
- // Check if there is mentions
- if mentionsIndexes.count != 0 {
- var indexDiff = 0
- for (index,dict) in mentionsIndexes {
- let length = dict["length"] as! Int
- if case index+1 ... index+length-1 = range.location {
- // If start typing within a mention rang delete that name:
- mentionsIndexes.removeValue(forKey: index)
- indexDiff += -length
- textView.replace(textView.textRangeFromNSRange(range: NSMakeRange(index, length))!, withText: "")
- }
- else if (range.location + range.length < index+length) && (range.location + range.length > index) {
- mentionsIndexes.removeValue(forKey: index)
- }
- else if (index > range.location && index+length <= range.location + range.length) || (range.location < index + length && range.location + range.length >= index+length) {
- mentionsIndexes.removeValue(forKey: index)
- }
- else if index >= range.location && range.length == 0 {
- mentionsIndexes.removeValue(forKey: index)
- mentionsIndexes[index + indexDiff + text.utf16.count] = dict
- }
- else if index >= range.location && range.length > 0 {
- mentionsIndexes.removeValue(forKey: index)
- mentionsIndexes[index + indexDiff + text.utf16.count - range.length] = dict
- }
- else if index < 0 {
- mentionsIndexes.removeValue(forKey: index)
- }
-
- if case index+length = range.location {
- // If start typing within a mention rang delete that name:
- delegate?.textViewShouldUpdateTextColor()
- }
- }
- }
-
- if isMentioning {
- if text == " " || (text.count == 0 && self.mentionQuery == ""){ // If Space or delete the "@"
- self.mentionQuery = ""
- self.isMentioning = false
- updateTableView()
- UIView.animate(withDuration: 0.2, animations: {
-
- self.tableView.isHidden = true
-
- })
- }
- else if text.count == 0 {
- self.mentionQuery.remove(at: self.mentionQuery.index(before: self.mentionQuery.endIndex))
- updateTableView()
- }
- else {
- self.mentionQuery += text
- updateTableView()
- }
- } else {
- if text == self.mentionCharater && ( range.location == 0 || lastCharacter == " " || range.length == 0 ) { /* (Beginning of textView) OR (space then @) */
-
- self.isMentioning = true
- self.startMentionIndex = range.location
- updateTableView()
- UIView.animate(withDuration: 0.2, animations: {
- self.tableView.isHidden = false
- })
- }
- }
-
- return true
- }
-
-
- //MARK: Keyboard will show NSNotification:
-
- @objc func keyboardWillShow(notification:NSNotification) {
-
- let userInfo:NSDictionary = notification.userInfo! as NSDictionary
- let keyboardFrame:NSValue = userInfo.value(forKey: UIResponder.keyboardFrameEndUserInfoKey) as! NSValue
- let keyboardRectangle = keyboardFrame.cgRectValue
- let thekeyboardHeight = keyboardRectangle.height
- self.keyboardHieght = thekeyboardHeight
-
- UIView.animate(withDuration: 0.3, animations: {
-
- self.updatePosition()
-
- })
-
- }
-
-
- //Mentions UITableView init
- func initMentionsList(){
-
- tableView = UITableView(frame: CGRect(x: 0, y: 0, width: self.mainView!.frame.width, height: 100), style: UITableView.Style.plain)
- tableView.delegate = self
- tableView.dataSource = self
- tableView.tableFooterView = UIView()
- tableView.allowsSelection = true
- tableView.separatorColor = UIColor.clear
- tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
- self.mainView!.addSubview(self.tableView)
-
- self.tableView.isHidden = true
- }
-
-
- //MARK: Mentions UITableView deleget functions:
-
- func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- return self.filteredOEObjects!.count
- }
-
- func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
- var cell:UITableViewCell?
- if delegate != nil {
- cell = delegate?.tableView(tableView, cellForRowAt: indexPath, oeObject: filteredOEObjects![indexPath.row])
- if cell != nil {
- return cell!
- }
- }
-
- cell = UITableViewCell(style: UITableViewCell.CellStyle.subtitle, reuseIdentifier: "cell")
- cell!.backgroundColor = UIColor.clear
- cell!.selectionStyle = UITableViewCell.SelectionStyle.none
- cell!.textLabel!.text = filteredOEObjects![indexPath.row].name
- cell!.textLabel!.textColor = nameColor
-
- return cell!
- }
-
- func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
- guard let selectedMention = filteredOEObjects?[indexPath.row] else {
- return
- }
-
- addMentionToTextView(oeObject: selectedMention)
-
- self.mentionQuery = ""
- self.isMentioning = false
- UIView.animate(withDuration: 0.2, animations: {
- self.tableView.isHidden = true
- })
-
- if delegate != nil {
- self.delegate!.mentionSelected(id: selectedMention.id, name: selectedMention.name)
- }
- }
-
- // Add a mention name to the UITextView
- func addMentionToTextView(oeObject: OEObject){
- let name = oeObject.name as String
-
- // add a space at the end and beginning of the mention (if needed)
- var mentionBeginChar = ""
- var mentionEndChat = ""
- if self.startMentionIndex > 0 {
- if let range = Range.init(NSMakeRange(self.startMentionIndex - 1, 1), in: self.textView!.text) {
- if self.textView!.text[range] != " " {
- mentionBeginChar = " "
- }
- }
- }
- if self.startMentionIndex + self.mentionQuery.count + 1 < self.textView!.text.utf16.count {
- if let range = Range.init(NSMakeRange(self.startMentionIndex + self.mentionQuery.count + 1, 1), in: self.textView!.text) {
- if self.textView!.text[range] != " " {
- mentionEndChat = " "
- }
- }
- } else {
- if self.startMentionIndex + self.mentionQuery.count + 1 == self.textView!.text.utf16.count {
- mentionEndChat = " "
- }
- }
-
- let dict = ["key": oeObject.key, "length": name.utf16.count] as [String : Any]
- let newStartMentionIndex = mentionBeginChar.utf16.count > 0 ? self.startMentionIndex+1 : self.startMentionIndex
- mentionsIndexes[newStartMentionIndex] = dict
-
- let range = NSRange.init(location: self.startMentionIndex, length: self.mentionQuery.count + 1)
- let swiftRange = Range.init(range, in: self.textView!.text)
- let replaceString = mentionBeginChar + oeObject.name + mentionEndChat
- self.textView!.text.replaceSubrange(swiftRange!, with: replaceString)
-
- let indexDiff = replaceString.utf16.count - 1
- if mentionsIndexes.count != 0 {
- for (index,dict) in mentionsIndexes {
- if index != newStartMentionIndex {
- if index > range.location && range.length == 0 {
- mentionsIndexes.removeValue(forKey: index)
- mentionsIndexes[index + indexDiff] = dict
- }
- else if index > range.location && range.length > 0 {
- mentionsIndexes.removeValue(forKey: index)
- mentionsIndexes[index + indexDiff] = dict
- }
- }
- }
- }
-
- if let theText = self.textView!.text {
- var attributes = [NSAttributedString.Key: AnyObject]()
- attributes[.foregroundColor] = notMentionColor
- attributes[.font] = nameFont
-
- let attributedString: NSMutableAttributedString = NSMutableAttributedString.init(string: theText, attributes: attributes)
-
- self.textView!.attributedText = attributedString
- if let cursorLocation = self.textView!.position(from: self.textView!.beginningOfDocument, offset: self.startMentionIndex + name.utf16.count + 1) {
- self.textView!.selectedTextRange = self.textView!.textRange(from: cursorLocation, to: cursorLocation)
- }
- }
-
- updatePosition()
- }
-
-
- // Update views potision for the textview and tableview
- func updatePosition(){
- if keyboardHieght == nil {
- return
- }
- if #available(iOS 11.0, *) {
- self.tableView.frame.size.width = mainView!.safeAreaLayoutGuide.layoutFrame.size.width
- self.tableView.frame.origin.x = mainView!.safeAreaLayoutGuide.layoutFrame.origin.x
- } else {
- self.tableView.frame.size.width = mainView!.frame.size.width
- self.tableView.frame.origin.x = mainView!.frame.origin.x
- }
- if containerView != nil {
-
- self.textView!.frame.origin.y = self.textViewYPosition!
- let fullTableViewHeight = UIScreen.main.bounds.height - self.keyboardHieght! - self.containerView!.frame.size.height
- if showMentionFullInContainer == true {
- if fullTableViewHeight != self.tableView.frame.size.height {
- self.tableView.frame.size.height = fullTableViewHeight
- self.tableView.frame.origin.y = 0
- }
- } else {
- if self.tableView.contentSize.height < fullTableViewHeight {
- self.tableView.frame.size.height = self.tableView.contentSize.height
- self.tableView.frame.origin.y = UIScreen.main.bounds.height - self.keyboardHieght! - containerView!.frame.size.height - self.tableView.frame.size.height
- } else {
- if fullTableViewHeight != self.tableView.frame.size.height {
- self.tableView.frame.size.height = fullTableViewHeight
- self.tableView.frame.origin.y = 0
- }
- }
- }
- }
- else {
- self.textView!.frame.origin.y = UIScreen.main.bounds.height - self.keyboardHieght! - self.textView!.frame.height
- self.tableView.frame.size.height = UIScreen.main.bounds.height - self.keyboardHieght! - self.textView!.frame.height
- self.tableView.frame.origin.y = 0
- }
- if delegate != nil {
- delegate!.tableViewPositionUpdated()
- }
- }
-
- private func updateTableView() {
- if mentionQuery.count > 0 {
- self.filteredOEObjects = self.oeObjects?.filter {
- $0.name.lowercased().localizedStandardContains(self.mentionQuery.lowercased()) || $0.key.lowercased().localizedStandardContains(self.mentionQuery.lowercased())
- }
- } else {
- self.filteredOEObjects = oeObjects
- }
-
- self.tableView.reloadData()
- }
- }
- // OEMentions object (id,name)
- class OEObject {
-
- var id:Int
- var name:String
- var key:String
- var object: Any?
-
- init(id:Int, name:String, key: String, object: Any?){
- self.id = id
- self.name = name
- self.key = key
- self.object = object
- }
- }
- extension UITextView
- {
- func textRangeFromNSRange(range:NSRange) -> UITextRange?
- {
- let beginning = self.beginningOfDocument
- guard let start = self.position(from: beginning, offset: range.location), let end = self.position(from: start, offset: range.length) else { return nil}
- return self.textRange(from: start, to: end)
- }
- }
|