Skip to content

Instantly share code, notes, and snippets.

@darko55555
Last active November 22, 2018 10:15
Show Gist options
  • Save darko55555/0330361dd6f68208f71a75d581cd4559 to your computer and use it in GitHub Desktop.
Save darko55555/0330361dd6f68208f71a75d581cd4559 to your computer and use it in GitHub Desktop.
Chat mentions solution made with Rx
var mention:Observable<[FRep]>!
var rightSideTrimmedTextLastWord = String()
var lastTypedMention = String()
var lastTypedMentionRange:Range<String.Index>?
func observeMessageText(){
mention = messageText
.flatMap{ [weak self] inputText -> Observable<[FRep]> in
if inputText.isEmpty {
return .just([])
}
//get's text up to the textinput cursor
let textWithRemovedRightSide = inputText[inputText.utf16.startIndex..<inputText.utf16.index(inputText.utf16.startIndex, offsetBy: self!.textview.selectedRange.upperBound)]
//first @ from the left side to the last index - searching to the left
if let lastTypedMentionRange = String(textWithRemovedRightSide)
.range(of: "@", options: String.CompareOptions.backwards, range: nil, locale: nil){
let range = lastTypedMentionRange.lowerBound..<textWithRemovedRightSide.endIndex
self?.lastTypedMentionRange = range
//Check if there is a character before @ and hide mention suggestion if there is
if range.lowerBound > inputText.utf16.startIndex{
let characterBeforeTheMentionStart = inputText[inputText.utf16.index(range.lowerBound, offsetBy: -1)]
if characterBeforeTheMentionStart != " " { return .just([]) }
}
self?.lastTypedMention = String(inputText[range])
if (self?.chat.members
.filter{ $0.hashedEmail != FirebaseConstants.myUserId }
.compactMap{$0.name}
.contains(self?.lastTypedMention.replacingOccurrences(of: "@", with: "")))!{
self?.toggleMentionsView(toState: .closed)
self?.filteredUsers.removeAll()
return .just([])
}
self?.currentMention = String(inputText[range])
self?.filteredUsers = (self?.chatMembersValidForMentions
.compactMap{$0}
.filter{$0.name != nil}
.filter{($0.name!.lowercased()
.hasPrefix((self?.lastTypedMention.lowercased()
.replacingOccurrences(of: "@", with: ""))!))})!
}else{
return Observable.just([])
}
return Observable.just(self!.filteredUsers)
}
mention
.subscribe(onNext:{ [weak self] in
!$0.isEmpty ? self?.toggleMentionsView(toState: .open) : self?.toggleMentionsView(toState: .closed)
})
.disposed(by: disposeBag)
}
enum MentionsViewState{
case open, closed
}
func toggleMentionsView(toState state:MentionsViewState){
switch state {
case .closed:
mentionsViewHeightContraint?.constant = CGFloat(0.0)
return
case .open:
DispatchQueue.main.async {[weak self] in
//4.5 because the agreement is to show 4.5 rows so the user knows they can scroll
guard let mentionsRowHeight = self?.mentionsRowHeight,
let numberOfUsers = self?.filteredUsers.count else { return }
let mentionsViewExpandedHeight = min(Double(numberOfUsers)*mentionsRowHeight,4.5*mentionsRowHeight)
UIView.animate(withDuration: 0.22, delay: 0.1, usingSpringWithDamping: 2.5, initialSpringVelocity: 7, options: .curveEaseInOut, animations: {
self?.mentionsViewHeightContraint?.constant = CGFloat(mentionsViewExpandedHeight)
self?.view.layoutIfNeeded()
self?.didShowMentionsView = true
}, completion: nil)
return
}
}
}
@darko55555
Copy link
Author

Solution where you only need to provide two datasources

  1. Text from the inputfield
  2. Set of chat participants

What is great about this solution is that your names can contain emojis, zalgo or any other imaginable combination, it'll work.

Any improvements suggestion is greatly appreciated :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment