From 0078f39544a43e1f5ee968c676b6ce37ef8391ff Mon Sep 17 00:00:00 2001 From: WEI-JEN TU Date: Wed, 16 Mar 2016 16:26:40 -0700 Subject: [PATCH] An implementation of Swift Example - Implement MessageViewController (Swift) - Fix a bug that UILongPressGestureRecognizer is added multiple times (ObjC) - Fix a bug that Attempt to present UIAlertController on UINavigationController which is already presenting UIAlertController (ObjC) --- Examples/Messenger-Shared/Bridge-Header.h | 9 +- .../Messenger-Shared/MessageViewController.m | 40 +- .../MessageViewController.swift | 656 +++++++++++++++++- Examples/Messenger.xcodeproj/project.pbxproj | 6 + 4 files changed, 685 insertions(+), 26 deletions(-) diff --git a/Examples/Messenger-Shared/Bridge-Header.h b/Examples/Messenger-Shared/Bridge-Header.h index 4c61c000..28e2cd85 100644 --- a/Examples/Messenger-Shared/Bridge-Header.h +++ b/Examples/Messenger-Shared/Bridge-Header.h @@ -2,4 +2,11 @@ // Use this file to import your target's public headers that you would like to expose to Swift. // -#import "SLKTextViewController.h" +#import + +#import "Message.h" +#import "MessageTableViewCell.h" +#import "MessageTextView.h" +#import "TypingIndicatorView.h" + +#import \ No newline at end of file diff --git a/Examples/Messenger-Shared/MessageViewController.m b/Examples/Messenger-Shared/MessageViewController.m index d309741e..d8f60602 100644 --- a/Examples/Messenger-Shared/MessageViewController.m +++ b/Examples/Messenger-Shared/MessageViewController.m @@ -239,27 +239,29 @@ - (void)simulateUserTyping:(id)sender - (void)didLongPressCell:(UIGestureRecognizer *)gesture { + if (gesture.state == UIGestureRecognizerStateEnded) { #ifdef __IPHONE_8_0 - if (SLK_IS_IOS8_AND_HIGHER && [UIAlertController class]) { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; - alertController.modalPresentationStyle = UIModalPresentationPopover; - alertController.popoverPresentationController.sourceView = gesture.view.superview; - alertController.popoverPresentationController.sourceRect = gesture.view.frame; - - [alertController addAction:[UIAlertAction actionWithTitle:@"Edit Message" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + if (SLK_IS_IOS8_AND_HIGHER && [UIAlertController class]) { + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; + alertController.modalPresentationStyle = UIModalPresentationPopover; + alertController.popoverPresentationController.sourceView = gesture.view.superview; + alertController.popoverPresentationController.sourceRect = gesture.view.frame; + + [alertController addAction:[UIAlertAction actionWithTitle:@"Edit Message" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + [self editCellMessage:gesture]; + }]]; + + [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:NULL]]; + + [self.navigationController presentViewController:alertController animated:YES completion:nil]; + } + else { [self editCellMessage:gesture]; - }]]; - - [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:NULL]]; - - [self.navigationController presentViewController:alertController animated:YES completion:nil]; - } - else { - [self editCellMessage:gesture]; - } + } #else - [self editCellMessage:gesture]; + [self editCellMessage:gesture]; #endif + } } - (void)editCellMessage:(UIGestureRecognizer *)gesture @@ -572,11 +574,11 @@ - (MessageTableViewCell *)messageCellForRowAtIndexPath:(NSIndexPath *)indexPath { MessageTableViewCell *cell = (MessageTableViewCell *)[self.tableView dequeueReusableCellWithIdentifier:MessengerCellIdentifier]; - if (!cell.textLabel.text) { + if (cell.gestureRecognizers.count == 0) { UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(didLongPressCell:)]; [cell addGestureRecognizer:longPress]; } - + Message *message = self.messages[indexPath.row]; cell.titleLabel.text = message.username; diff --git a/Examples/Messenger-Swift/MessageViewController.swift b/Examples/Messenger-Swift/MessageViewController.swift index 5f174e3b..cf6b2d53 100644 --- a/Examples/Messenger-Swift/MessageViewController.swift +++ b/Examples/Messenger-Swift/MessageViewController.swift @@ -6,19 +6,663 @@ // Copyright (c) 2014 Slack Technologies, Inc. All rights reserved. // -class MessageViewController: SLKTextViewController { +let DEBUG_CUSTOM_TYPING_INDICATOR = false +class MessageViewController: SLKTextViewController { + + var messages = [Message]() + + var users = ["Allen", "Anna", "Alicia", "Arnold", "Armando", "Antonio", "Brad", "Catalaya", "Christoph", "Emerson", "Eric", "Everyone", "Steve"] + var channels = ["General", "Random", "iOS", "Bugs", "Sports", "Android", "UI", "SSB"] + var emojis = ["-1", "m", "man", "machine", "block-a", "block-b", "bowtie", "boar", "boat", "book", "bookmark", "neckbeard", "metal", "fu", "feelsgood"] + var commands = ["msg", "call", "text", "skype", "kick", "invite"] + + var searchResult: [AnyObject]? + + var pipWindow: UIWindow? + + var editingMessage = Message() + + // MARK: - Lifeterm + deinit { + NSNotificationCenter.defaultCenter().removeObserver(self) + } + override class func tableViewStyleForCoder(decoder: NSCoder) -> UITableViewStyle { - return UITableViewStyle.Plain; + return .Plain } - override func viewDidLoad() { + func commonInit() { + NSNotificationCenter.defaultCenter().addObserver(self.tableView, + selector: "reloadData", + name: UIContentSizeCategoryDidChangeNotification, + object: nil) + NSNotificationCenter.defaultCenter().addObserver(self, + selector: "textInputbarDidMove:", + name: SLKTextInputbarDidMoveNotification, + object: nil) + + // Register a SLKTextView subclass, if you need any special appearance and/or behavior customisation. + self.registerClassForTextView(MessageTextView.classForCoder()) - // In progress in branch 'swift-example' + if DEBUG_CUSTOM_TYPING_INDICATOR == true { + // Register a UIView subclass, conforming to SLKTypingIndicatorProtocol, to use a custom typing indicator view. + self.registerClassForTypingIndicatorView(TypingIndicatorView.classForCoder()) + } + } + + override func viewDidLoad() { super.viewDidLoad() + + self.commonInit() + + // Example's configuration + self.configureDataSource() + self.configureActionItems() + + // SLKTVC's configuration + self.bounces = true + self.shakeToClearEnabled = true + self.keyboardPanningEnabled = true + self.shouldScrollToBottomAfterKeyboardShows = false + self.inverted = true + + self.leftButton.setImage(UIImage(named: "icn_upload"), forState: .Normal) + self.leftButton.tintColor = UIColor.grayColor() + + self.rightButton.setTitle(NSLocalizedString("Send", comment: ""), forState: .Normal) + + + self.textInputbar.autoHideRightButton = true + self.textInputbar.maxCharCount = 256 + self.textInputbar.counterStyle = .Split + self.textInputbar.counterPosition = .Top + + self.textInputbar.editorTitle.textColor = UIColor.darkGrayColor() + self.textInputbar.editorLeftButton.tintColor = UIColor(red: 0/255, green: 122/255, blue: 255/255, alpha: 1) + self.textInputbar.editorRightButton.tintColor = UIColor(red: 0/255, green: 122/255, blue: 255/255, alpha: 1) + + if DEBUG_CUSTOM_TYPING_INDICATOR == false { + self.typingIndicatorView.canResignByTouch = true + } + + self.tableView.separatorStyle = .None + self.tableView.registerClass(MessageTableViewCell.classForCoder(), forCellReuseIdentifier: MessengerCellIdentifier) + + self.autoCompletionView.registerClass(MessageTableViewCell.classForCoder(), forCellReuseIdentifier: AutoCompletionCellIdentifier) + self.registerPrefixesForAutoCompletion(["@", "#", ":", "+:", "/"]) + + self.textView.registerMarkdownFormattingSymbol("*", withTitle: "Bold") + self.textView.registerMarkdownFormattingSymbol("_", withTitle: "Italics") + self.textView.registerMarkdownFormattingSymbol("~", withTitle: "Strike") + self.textView.registerMarkdownFormattingSymbol("`", withTitle: "Code") + self.textView.registerMarkdownFormattingSymbol("```", withTitle: "Preformatted") + self.textView.registerMarkdownFormattingSymbol(">", withTitle: "Quote") } - + override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } +} + +extension MessageViewController { + + // MARK: - Example's Configuration + + func configureDataSource() { + var array = [Message]() + + for _ in 0..<100 { + let words = Int((arc4random() % 40)+1) + let message = Message() + message.username = LoremIpsum.name() + message.text = LoremIpsum.wordsWithNumber(words) + array.append(message) + } + + let reversed = array.reverse() + + self.messages.appendContentsOf(reversed) + } + + func configureActionItems() { + let arrowItem = UIBarButtonItem(image: UIImage(named: "icn_arrow_down"), style: .Plain, target: self, action: "hideOrShowTextInputbar:") + let editItem = UIBarButtonItem(image: UIImage(named: "icn_editing"), style: .Plain, target: self, action: "editRandomMessage:") + let typeItem = UIBarButtonItem(image: UIImage(named: "icn_typing"), style: .Plain, target: self, action: "simulateUserTyping:") + let appendItem = UIBarButtonItem(image: UIImage(named: "icn_append"), style: .Plain, target: self, action: "fillWithText:") + let pipItem = UIBarButtonItem(image: UIImage(named: "icn_pic"), style: .Plain, target: self, action: "togglePIPWindow:") + self.navigationItem.rightBarButtonItems = [arrowItem, pipItem, editItem, appendItem, typeItem] + } + + // MARK: - Action Methods + + func hideOrShowTextInputbar(sender: AnyObject) { + let hide = !self.textInputbarHidden + + let image = hide ? UIImage(named: "icn_arrow_up") : UIImage(named: "icn_arrow_down") + + guard let buttonItem = sender as? UIBarButtonItem else { + return + } + self.setTextInputbarHidden(hide, animated: true) + buttonItem.image = image + } + + func fillWithText(sender: AnyObject) { + if self.textView.text.characters.count == 0 { + var sentences = Int(arc4random() % 4) + if sentences <= 1 { + sentences = 1 + } + self.textView.text = LoremIpsum.sentencesWithNumber(sentences) + } else { + self.textView.slk_insertTextAtCaretRange(" " + LoremIpsum.word()) + } + } + + func simulateUserTyping(sender: AnyObject) { + if self.canShowTypingIndicator() { + if DEBUG_CUSTOM_TYPING_INDICATOR == true { + guard let view = self.typingIndicatorProxyView as? TypingIndicatorView else { + return + } + + let scale = UIScreen.mainScreen().scale + let imgSize = CGSizeMake(kTypingIndicatorViewAvatarHeight*scale, kTypingIndicatorViewAvatarHeight*scale) + + // This will cause the typing indicator to show after a delay ¯\_(ツ)_/¯ + LoremIpsum.asyncPlaceholderImageWithSize(imgSize, completion: { (image) -> Void in + guard let cgImage = image.CGImage else { + return + } + let thumbnail = UIImage(CGImage: cgImage, scale: scale, orientation: .Up) + view.presentIndicatorWithName(LoremIpsum.name(), image: thumbnail) + }) + } else { + self.typingIndicatorView.insertUsername(LoremIpsum.name()) + } + } + } + + func didLongPressCell(gesture: UIGestureRecognizer) { + if gesture.state == .Ended { + if #available(iOS 8, *) { + guard let view = gesture.view else { + return + } + let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .ActionSheet) + alertController.modalPresentationStyle = .Popover + alertController.popoverPresentationController?.sourceView = view.superview + alertController.popoverPresentationController?.sourceRect = view.frame + + alertController.addAction(UIAlertAction(title: "Edit Message", style: .Default, handler: { [unowned self] (action) -> Void in + self.editCellMessage(gesture) + })) + + alertController.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: nil)) + + self.navigationController?.presentViewController(alertController, animated: true, completion: nil) + } else { + self.editCellMessage(gesture) + } + } + } + + func editCellMessage(gesture: UIGestureRecognizer) { + guard let cell = gesture.view as? MessageTableViewCell else { + return + } + + self.editingMessage = self.messages[cell.indexPath.row] + + self.editText(self.editingMessage.text) + + self.tableView.scrollToRowAtIndexPath(cell.indexPath, atScrollPosition: .Bottom, animated: true) + } + + func editRandomMessage(sender: AnyObject) { + var sentences = Int(arc4random() % 10) + if sentences <= 1 { + sentences = 1 + } + self.editText(LoremIpsum.sentencesWithNumber(sentences)) + } + + func editLastMessage(sender: AnyObject?) { + if self.textView.text.characters.count > 0 { + return + } + + let lastSectionIndex = self.tableView.numberOfSections-1 + let lastRowIndex = self.tableView.numberOfRowsInSection(lastSectionIndex)-1 + + let lastMessage = self.messages[lastRowIndex] + + self.editText(lastMessage.text) + + self.tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: lastRowIndex, inSection: lastSectionIndex), atScrollPosition: .Bottom, animated: true) + } + + func togglePIPWindow(sender: AnyObject) { + if self.pipWindow == nil { + self.showPIPWindow(sender) + } else { + self.hidePIPWindow(sender) + } + } + + func showPIPWindow(sender: AnyObject) { + var frame = CGRectMake(CGRectGetWidth(self.view.frame) - 60.0, 0.0, 50.0, 50.0) + frame.origin.y = CGRectGetMinY(self.textInputbar.frame) - 60.0 + + self.pipWindow = UIWindow(frame: frame) + self.pipWindow?.backgroundColor = UIColor.blackColor() + self.pipWindow?.layer.cornerRadius = 10 + self.pipWindow?.layer.masksToBounds = true + self.pipWindow?.hidden = false + self.pipWindow?.alpha = 0.0 + + UIApplication.sharedApplication().keyWindow?.addSubview(self.pipWindow!) + + UIView.animateWithDuration(0.25) { [unowned self] () -> Void in + self.pipWindow?.alpha = 1.0 + } + } + + func hidePIPWindow(sender: AnyObject) { + UIView.animateWithDuration(0.3, animations: { [unowned self] () -> Void in + self.pipWindow?.alpha = 0.0 + }) { [unowned self] (finished) -> Void in + self.pipWindow?.hidden = true + self.pipWindow = nil + } + } + + func textInputbarDidMove(note: NSNotification) { + + guard let pipWindow = self.pipWindow else { + return + } + + guard let userInfo = note.userInfo else { + return + } + + guard let value = userInfo["origin"] as? NSValue else { + return + } + + var frame = pipWindow.frame + frame.origin.y = value.CGPointValue().y - 60.0 + + pipWindow.frame = frame + } +} + +extension MessageViewController { + + // MARK: - Overriden Methods + + override func ignoreTextInputbarAdjustment() -> Bool { + return super.ignoreTextInputbarAdjustment() + } + + override func forceTextInputbarAdjustmentForResponder(responder: UIResponder!) -> Bool { + if #available(iOS 8.0, *) { + guard let _ = responder as? UIAlertController else { + // On iOS 9, returning YES helps keeping the input view visible when the keyboard if presented from another app when using multi-tasking on iPad. + return UIDevice.currentDevice().userInterfaceIdiom == .Pad + } + return true + } else { + return UIDevice.currentDevice().userInterfaceIdiom == .Pad + } + } + + override func didChangeKeyboardStatus(status: SLKKeyboardStatus) { + // Notifies the view controller that the keyboard changed status. + } + + override func textWillUpdate() { + // Notifies the view controller that the text will update. + super.textWillUpdate() + } + + override func textDidUpdate(animated: Bool) { + // Notifies the view controller that the text did update. + super.textDidUpdate(animated) + } + + override func didPressLeftButton(sender: AnyObject!) { + // Notifies the view controller when the left button's action has been triggered, manually. + super.didPressLeftButton(sender) + } + + override func didPressRightButton(sender: AnyObject!) { + // Notifies the view controller when the right button's action has been triggered, manually or by using the keyboard return key. + + // This little trick validates any pending auto-correction or auto-spelling just after hitting the 'Send' button + + self.textView.refreshFirstResponder() + + let message = Message() + message.username = LoremIpsum.name() + message.text = self.textView.text + + let indexPath = NSIndexPath(forRow: 0, inSection: 0) + let rowAnimation: UITableViewRowAnimation = self.inverted ? .Bottom : .Top + let scrollPosition: UITableViewScrollPosition = self.inverted ? .Bottom : .Top + + self.tableView.beginUpdates() + self.messages.insert(message, atIndex: 0) + self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: rowAnimation) + self.tableView.endUpdates() + + self.tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: scrollPosition, animated: true) + + // Fixes the cell from blinking (because of the transform, when using translucent cells) + // See https://github.com/slackhq/SlackTextViewController/issues/94#issuecomment-69929927 + self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) + + super.didPressRightButton(sender) + } + + override func didPressArrowKey(keyCommand: UIKeyCommand!) { + if keyCommand.input == UIKeyInputUpArrow && self.textView.text.characters.count == 0 { + self.editLastMessage(nil) + } else { + super.didPressArrowKey(keyCommand) + } + } + + override func keyForTextCaching() -> String! { + return NSBundle.mainBundle().bundleIdentifier + } + + override func didPasteMediaContent(userInfo: [NSObject : AnyObject]!) { + // Notifies the view controller when the user has pasted a media (image, video, etc) inside of the text view. + super.didPasteMediaContent(userInfo) + + let mediaType = userInfo[SLKTextViewPastedItemMediaType]?.integerValue + let contentType = userInfo[SLKTextViewPastedItemContentType] + let data = userInfo[SLKTextViewPastedItemData] + + print("\(__FUNCTION__) : \(contentType) (type = \(mediaType) | data : \(data))") + } + + override func willRequestUndo() { + // Notifies the view controller when a user did shake the device to undo the typed text + super.willRequestUndo() + } + + override func didCommitTextEditing(sender: AnyObject!) { + // Notifies the view controller when tapped on the right "Accept" button for commiting the edited text + //self.editingMessage.text = self.textView.text + + self.tableView.reloadData() + + super.didCommitTextEditing(sender) + } + + override func didCancelTextEditing(sender: AnyObject!) { + // Notifies the view controller when tapped on the left "Cancel" button + + super.didCancelTextEditing(sender) + } + + override func canPressRightButton() -> Bool { + return super.canPressRightButton() + } + + override func canShowTypingIndicator() -> Bool { + if DEBUG_CUSTOM_TYPING_INDICATOR == true { + return true + } else { + return super.canShowTypingIndicator() + } + } + + override func shouldProcessTextForAutoCompletion(text: String!) -> Bool { + return true + } + + override func didChangeAutoCompletionPrefix(prefix: String!, andWord word: String!) { + var array: [AnyObject]? + + self.searchResult = nil + + if prefix == "@" { + if word.characters.count > 0 { + array = (self.users as NSArray).filteredArrayUsingPredicate(NSPredicate(format: "self BEGINSWITH[c] %@", word)) + } else { + array = self.users + } + } else if prefix == "#" && word.characters.count > 0 { + array = (self.channels as NSArray).filteredArrayUsingPredicate(NSPredicate(format: "self BEGINSWITH[c] %@", word)) + } else if (prefix == ":" || prefix == "+:") && word.characters.count > 0 { + array = (self.emojis as NSArray).filteredArrayUsingPredicate(NSPredicate(format: "self BEGINSWITH[c] %@", word)) + } else if prefix == "/" && self.foundPrefixRange.location == 0 { + if word.characters.count > 0 { + array = (self.commands as NSArray).filteredArrayUsingPredicate(NSPredicate(format: "self BEGINSWITH[c] %@", word)) + } else { + array = self.commands + } + } + + if array?.count == 0 { + return + } + + self.searchResult = (array! as NSArray).sortedArrayUsingSelector("localizedCaseInsensitiveCompare:") + + let show = (self.searchResult?.count > 0) + + self.showAutoCompletionView(show) + } + + override func heightForAutoCompletionView() -> CGFloat { + guard let searchResult = self.searchResult else { + return 0 + } + + let cellHeight = self.autoCompletionView.delegate?.tableView!(self.autoCompletionView, heightForRowAtIndexPath: NSIndexPath(forRow: 0, inSection: 0)) + guard let height = cellHeight else { + return 0 + } + return height * CGFloat(searchResult.count) + } +} + +extension MessageViewController { + + // MARK: - UITableViewDataSource Methods + + override func numberOfSectionsInTableView(tableView: UITableView) -> Int { + return 1 + } + + override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if tableView == self.tableView { + return self.messages.count + } else { + guard let searchResult = self.searchResult else { + return 0 + } + return searchResult.count + } + } + + override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { + if tableView == self.tableView { + return self.messageCellForRowAtIndexPath(indexPath) + } else { + return self.autoCompletionCellForRowAtIndexPath(indexPath) + } + } + + func messageCellForRowAtIndexPath(indexPath: NSIndexPath) -> MessageTableViewCell { + let cell = self.tableView.dequeueReusableCellWithIdentifier(MessengerCellIdentifier) as! MessageTableViewCell + + if cell.gestureRecognizers?.count == nil { + let longPress = UILongPressGestureRecognizer(target: self, action: "didLongPressCell:") + cell.addGestureRecognizer(longPress) + } + + let message = self.messages[indexPath.row] + + cell.titleLabel.text = message.username + cell.bodyLabel.text = message.text + + cell.indexPath = indexPath + cell.usedForMessage = true + + // Cells must inherit the table view's transform + // This is very important, since the main table view may be inverted + cell.transform = self.tableView.transform + + return cell + } + + func autoCompletionCellForRowAtIndexPath(indexPath: NSIndexPath) -> MessageTableViewCell { + let cell = self.autoCompletionView.dequeueReusableCellWithIdentifier(AutoCompletionCellIdentifier) as! MessageTableViewCell + cell.indexPath = indexPath + + guard let searchResult = self.searchResult as? [String] else { + return cell + } + + var text = searchResult[indexPath.row] + + if self.foundPrefix == "#" { + text = "# " + text + } else if self.foundPrefix == ":" || self.foundPrefix == "+:" { + text = ":\(text):" + } + + cell.titleLabel.text = text + cell.selectionStyle = .Default + + return cell + } + + override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { + if tableView == self.tableView { + let message = self.messages[indexPath.row] + + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.lineBreakMode = .ByWordWrapping + paragraphStyle.alignment = .Left + + let pointSize = MessageTableViewCell.defaultFontSize() + + let attributes = [ + NSFontAttributeName : UIFont.systemFontOfSize(pointSize), + NSParagraphStyleAttributeName : paragraphStyle + ] + + var width = CGRectGetWidth(tableView.frame)-kMessageTableViewCellAvatarHeight + width -= 25.0 + + let titleBounds = (message.username as NSString).boundingRectWithSize(CGSize(width: width, height: CGFloat.max), options: .UsesLineFragmentOrigin, attributes: attributes, context: nil) + let bodyBounds = (message.text as NSString).boundingRectWithSize(CGSize(width: width, height: CGFloat.max), options: .UsesLineFragmentOrigin, attributes: attributes, context: nil) + + if message.text.characters.count == 0 { + return 0 + } + + var height = CGRectGetHeight(titleBounds) + height += CGRectGetHeight(bodyBounds) + height += 40 + + + if height < kMessageTableViewCellMinimumHeight { + height = kMessageTableViewCellMinimumHeight + } + + return height + + } else { + return kMessageTableViewCellMinimumHeight + } + } + + // MARK: - UITableViewDelegate Methods + + override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { + if tableView == self.autoCompletionView { + + guard let searchResult = self.searchResult as? [String] else { + return + } + + var item = searchResult[indexPath.row] + + if self.foundPrefix == "@" && self.foundPrefixRange.location == 0 { + item += ":" + } else if self.foundPrefix == ":" || self.foundPrefix == "+:" { + item += ":" + } + + item += " " + + self.acceptAutoCompletionWithString(item, keepPrefix: true) + } + } +} + +extension MessageViewController { + + // MARK: - UIScrollViewDelegate Methods + + override func scrollViewDidScroll(scrollView: UIScrollView!) { + // Since SLKTextViewController uses UIScrollViewDelegate to update a few things, it is important that if you override this method, to call super. + super.scrollViewDidScroll(scrollView) + } + +} + +extension MessageViewController { + // MARK: - UITextViewDelegate Methods + + override func textViewShouldBeginEditing(textView: UITextView) -> Bool { + return true + } + + override func textViewShouldEndEditing(textView: UITextView) -> Bool { + return true + } + + override func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool { + return super.textView(textView, shouldChangeTextInRange: range, replacementText: text) + } + + override func textView(textView: SLKTextView, shouldOfferFormattingForSymbol symbol: String) -> Bool { + if symbol == ">" { + let selection = textView.selectedRange + + // The Quote formatting only applies new paragraphs + if selection.location == 0 && selection.length > 0 { + return true + } + + // or older paragraphs too + let prevString = (textView.text as NSString).substringWithRange(NSMakeRange(selection.location-1, 1)) + + if NSCharacterSet.newlineCharacterSet().characterIsMember((prevString as NSString).characterAtIndex(0)) { + return true + } + + return false + } + + return super.textView(textView, shouldOfferFormattingForSymbol: symbol) + } + + override func textView(textView: SLKTextView, shouldInsertSuffixForFormattingWithSymbol symbol: String, prefixRange: NSRange) -> Bool { + if symbol == ">" { + return false + } + return super.textView(textView, shouldInsertSuffixForFormattingWithSymbol: symbol, prefixRange: prefixRange) } -} \ No newline at end of file +} diff --git a/Examples/Messenger.xcodeproj/project.pbxproj b/Examples/Messenger.xcodeproj/project.pbxproj index 0d96c5fe..df4bf0ac 100644 --- a/Examples/Messenger.xcodeproj/project.pbxproj +++ b/Examples/Messenger.xcodeproj/project.pbxproj @@ -48,6 +48,9 @@ 71BE102F1A72F42F0083EE32 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4F86BF8E19F01234007A3D4A /* Images.xcassets */; }; 71BE10301A76C4890083EE32 /* MessageTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4F7C6BA41A6E208E006E3FAB /* MessageTextView.m */; }; 8C6D809F3C95C912F87F0D17 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DDFA9095EABC480682E6396C /* libPods.a */; }; + 9F4466C31C9A1B6B0001609A /* MessageTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 4F86BF9019F01234007A3D4A /* MessageTableViewCell.m */; }; + 9F4466C51C9A1B6B0001609A /* MessageTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4F7C6BA41A6E208E006E3FAB /* MessageTextView.m */; }; + 9F4466C71C9A1B6B0001609A /* TypingIndicatorView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FF04E581B3F7A04004C3BED /* TypingIndicatorView.m */; }; F556AFAE1BA7B41F009898BD /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F556AFAD1BA7B41F009898BD /* QuartzCore.framework */; }; F5DE01401B9679C4005E9082 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4F86BF7619F011D0007A3D4A /* LaunchScreen.xib */; }; /* End PBXBuildFile section */ @@ -651,6 +654,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 9F4466C31C9A1B6B0001609A /* MessageTableViewCell.m in Sources */, + 9F4466C51C9A1B6B0001609A /* MessageTextView.m in Sources */, + 9F4466C71C9A1B6B0001609A /* TypingIndicatorView.m in Sources */, 4F86BFCA19F050AF007A3D4A /* MessageViewController.swift in Sources */, 4F8ADA791A68C37400023752 /* Message.m in Sources */, 4F86BFC819F050AF007A3D4A /* AppDelegate.swift in Sources */,