http://git-wip-us.apache.org/repos/asf/usergrid/blob/7442c881/sdks/swift/Samples/ActivityFeed/Pods/SlackTextViewController/Source/SLKTextView.m ---------------------------------------------------------------------- diff --git a/sdks/swift/Samples/ActivityFeed/Pods/SlackTextViewController/Source/SLKTextView.m b/sdks/swift/Samples/ActivityFeed/Pods/SlackTextViewController/Source/SLKTextView.m new file mode 100644 index 0000000..a1433b7 --- /dev/null +++ b/sdks/swift/Samples/ActivityFeed/Pods/SlackTextViewController/Source/SLKTextView.m @@ -0,0 +1,1117 @@ +// +// Copyright 2014 Slack Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "SLKTextView.h" + +#import "SLKTextView+SLKAdditions.h" + +#import "SLKUIConstants.h" + +NSString * const SLKTextViewTextWillChangeNotification = @"SLKTextViewTextWillChangeNotification"; +NSString * const SLKTextViewContentSizeDidChangeNotification = @"SLKTextViewContentSizeDidChangeNotification"; +NSString * const SLKTextViewSelectedRangeDidChangeNotification = @"SLKTextViewSelectedRangeDidChangeNotification"; +NSString * const SLKTextViewDidPasteItemNotification = @"SLKTextViewDidPasteItemNotification"; +NSString * const SLKTextViewDidShakeNotification = @"SLKTextViewDidShakeNotification"; + +NSString * const SLKTextViewPastedItemContentType = @"SLKTextViewPastedItemContentType"; +NSString * const SLKTextViewPastedItemMediaType = @"SLKTextViewPastedItemMediaType"; +NSString * const SLKTextViewPastedItemData = @"SLKTextViewPastedItemData"; + +static NSString *const SLKTextViewGenericFormattingSelectorPrefix = @"slk_format_"; + +@interface SLKTextView () + +// The label used as placeholder +@property (nonatomic, strong) UILabel *placeholderLabel; + +// The initial font point size, used for dynamic type calculations +@property (nonatomic) CGFloat initialFontSize; + +// The keyboard commands available for external keyboards +@property (nonatomic, strong) NSArray *keyboardCommands; + +// Used for moving the caret up/down +@property (nonatomic) UITextLayoutDirection verticalMoveDirection; +@property (nonatomic) CGRect verticalMoveStartCaretRect; +@property (nonatomic) CGRect verticalMoveLastCaretRect; + +// Used for detecting if the scroll indicator was previously flashed +@property (nonatomic) BOOL didFlashScrollIndicators; + +@property (nonatomic, strong) NSMutableArray *registeredFormattingTitles; +@property (nonatomic, strong) NSMutableArray *registeredFormattingSymbols; +@property (nonatomic, getter=isFormatting) BOOL formatting; + +@end + +@implementation SLKTextView +@synthesize delegate = _delegate; + +#pragma mark - Initialization + +- (instancetype)initWithFrame:(CGRect)frame textContainer:(NSTextContainer *)textContainer +{ + if (self = [super initWithFrame:frame textContainer:textContainer]) { + [self slk_commonInit]; + } + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)coder +{ + if (self = [super initWithCoder:coder]) { + [self slk_commonInit]; + } + return self; +} + +- (void)slk_commonInit +{ + _pastableMediaTypes = SLKPastableMediaTypeNone; + _dynamicTypeEnabled = YES; + + self.undoManagerEnabled = YES; + self.autoCompleteFormatting = YES; + + self.editable = YES; + self.selectable = YES; + self.scrollEnabled = YES; + self.scrollsToTop = NO; + self.directionalLockEnabled = YES; + self.dataDetectorTypes = UIDataDetectorTypeNone; + + [self slk_registerNotifications]; + + [self addObserver:self forKeyPath:NSStringFromSelector(@selector(contentSize)) options:NSKeyValueObservingOptionNew context:NULL]; +} + + +#pragma mark - UIView Overrides + +- (CGSize)intrinsicContentSize +{ + CGFloat height = self.font.lineHeight; + height += self.textContainerInset.top + self.textContainerInset.bottom; + + return CGSizeMake(UIViewNoIntrinsicMetric, height); +} + ++ (BOOL)requiresConstraintBasedLayout +{ + return YES; +} + +- (void)layoutIfNeeded +{ + if (!self.window) { + return; + } + + [super layoutIfNeeded]; +} + +- (void)layoutSubviews +{ + [super layoutSubviews]; + + self.placeholderLabel.hidden = [self slk_shouldHidePlaceholder]; + + if (!self.placeholderLabel.hidden) { + + [UIView performWithoutAnimation:^{ + self.placeholderLabel.frame = [self slk_placeholderRectThatFits:self.bounds]; + [self sendSubviewToBack:self.placeholderLabel]; + }]; + } +} + + +#pragma mark - Getters + +- (UILabel *)placeholderLabel +{ + if (!_placeholderLabel) { + _placeholderLabel = [UILabel new]; + _placeholderLabel.clipsToBounds = NO; + _placeholderLabel.autoresizesSubviews = NO; + _placeholderLabel.numberOfLines = 1; + _placeholderLabel.font = self.font; + _placeholderLabel.backgroundColor = [UIColor clearColor]; + _placeholderLabel.textColor = [UIColor lightGrayColor]; + _placeholderLabel.hidden = YES; + + [self addSubview:_placeholderLabel]; + } + return _placeholderLabel; +} + +- (NSString *)placeholder +{ + return self.placeholderLabel.text; +} + +- (UIColor *)placeholderColor +{ + return self.placeholderLabel.textColor; +} + +- (NSUInteger)numberOfLines +{ + CGSize contentSize = self.contentSize; + + CGFloat contentHeight = contentSize.height; + contentHeight -= self.textContainerInset.top + self.textContainerInset.bottom; + + NSUInteger lines = fabs(contentHeight/self.font.lineHeight); + + // This helps preventing the content's height to be larger that the bounds' height + // Avoiding this way to have unnecessary scrolling in the text view when there is only 1 line of content + if (lines == 1 && contentSize.height > self.bounds.size.height) { + contentSize.height = self.bounds.size.height; + self.contentSize = contentSize; + } + + // Let's fallback to the minimum line count + if (lines == 0) { + lines = 1; + } + + return lines; +} + +- (NSUInteger)maxNumberOfLines +{ + NSUInteger numberOfLines = _maxNumberOfLines; + + if (SLK_IS_LANDSCAPE) { + if ((SLK_IS_IPHONE4 || SLK_IS_IPHONE5)) { + numberOfLines = 2.0; // 2 lines max on smaller iPhones + } + else if (SLK_IS_IPHONE) { + numberOfLines /= 2.0; // Half size on larger iPhone + } + } + + if (self.isDynamicTypeEnabled) { + NSString *contentSizeCategory = [[UIApplication sharedApplication] preferredContentSizeCategory]; + CGFloat pointSizeDifference = [SLKTextView pointSizeDifferenceForCategory:contentSizeCategory]; + + CGFloat factor = pointSizeDifference/self.initialFontSize; + + if (fabs(factor) > 0.75) { + factor = 0.75; + } + + numberOfLines -= floorf(numberOfLines * factor); // Calculates a dynamic number of lines depending of the user preferred font size + } + + return numberOfLines; +} + +- (BOOL)isTypingSuggestionEnabled +{ + return (self.autocorrectionType == UITextAutocorrectionTypeNo) ? NO : YES; +} + +- (BOOL)autoCompleteFormatting +{ + if (_registeredFormattingSymbols.count == 0) { + return NO; + } + return _autoCompleteFormatting; +} + +// Returns only a supported pasted item +- (id)slk_pastedItem +{ + NSString *contentType = [self slk_pasteboardContentType]; + NSData *data = [[UIPasteboard generalPasteboard] dataForPasteboardType:contentType]; + + if (data && [data isKindOfClass:[NSData class]]) + { + SLKPastableMediaType mediaType = SLKPastableMediaTypeFromNSString(contentType); + + NSDictionary *userInfo = @{SLKTextViewPastedItemContentType: contentType, + SLKTextViewPastedItemMediaType: @(mediaType), + SLKTextViewPastedItemData: data}; + return userInfo; + } + if ([[UIPasteboard generalPasteboard] URL]) { + return [[[UIPasteboard generalPasteboard] URL] absoluteString]; + } + if ([[UIPasteboard generalPasteboard] string]) { + return [[UIPasteboard generalPasteboard] string]; + } + + return nil; +} + +// Checks if any supported media found in the general pasteboard +- (BOOL)slk_isPasteboardItemSupported +{ + if ([self slk_pasteboardContentType].length > 0) { + return YES; + } + return NO; +} + +- (NSString *)slk_pasteboardContentType +{ + NSArray *pasteboardTypes = [[UIPasteboard generalPasteboard] pasteboardTypes]; + NSMutableArray *subpredicates = [NSMutableArray new]; + + for (NSString *type in [self slk_supportedMediaTypes]) { + [subpredicates addObject:[NSPredicate predicateWithFormat:@"SELF == %@", type]]; + } + + return [[pasteboardTypes filteredArrayUsingPredicate:[NSCompoundPredicate orPredicateWithSubpredicates:subpredicates]] firstObject]; +} + +- (NSArray *)slk_supportedMediaTypes +{ + if (self.pastableMediaTypes == SLKPastableMediaTypeNone) { + return nil; + } + + NSMutableArray *types = [NSMutableArray new]; + + if (self.pastableMediaTypes & SLKPastableMediaTypePNG) { + [types addObject:NSStringFromSLKPastableMediaType(SLKPastableMediaTypePNG)]; + } + if (self.pastableMediaTypes & SLKPastableMediaTypeJPEG) { + [types addObject:NSStringFromSLKPastableMediaType(SLKPastableMediaTypeJPEG)]; + } + if (self.pastableMediaTypes & SLKPastableMediaTypeTIFF) { + [types addObject:NSStringFromSLKPastableMediaType(SLKPastableMediaTypeTIFF)]; + } + if (self.pastableMediaTypes & SLKPastableMediaTypeGIF) { + [types addObject:NSStringFromSLKPastableMediaType(SLKPastableMediaTypeGIF)]; + } + if (self.pastableMediaTypes & SLKPastableMediaTypeMOV) { + [types addObject:NSStringFromSLKPastableMediaType(SLKPastableMediaTypeMOV)]; + } + if (self.pastableMediaTypes & SLKPastableMediaTypePassbook) { + [types addObject:NSStringFromSLKPastableMediaType(SLKPastableMediaTypePassbook)]; + } + + if (self.pastableMediaTypes & SLKPastableMediaTypeImages) { + [types addObject:NSStringFromSLKPastableMediaType(SLKPastableMediaTypeImages)]; + } + + return types; +} + +NSString *NSStringFromSLKPastableMediaType(SLKPastableMediaType type) +{ + if (type == SLKPastableMediaTypePNG) { + return @"public.png"; + } + if (type == SLKPastableMediaTypeJPEG) { + return @"public.jpeg"; + } + if (type == SLKPastableMediaTypeTIFF) { + return @"public.tiff"; + } + if (type == SLKPastableMediaTypeGIF) { + return @"com.compuserve.gif"; + } + if (type == SLKPastableMediaTypeMOV) { + return @"com.apple.quicktime"; + } + if (type == SLKPastableMediaTypePassbook) { + return @"com.apple.pkpass"; + } + if (type == SLKPastableMediaTypeImages) { + return @"com.apple.uikit.image"; + } + + return nil; +} + +SLKPastableMediaType SLKPastableMediaTypeFromNSString(NSString *string) +{ + if ([string isEqualToString:NSStringFromSLKPastableMediaType(SLKPastableMediaTypePNG)]) { + return SLKPastableMediaTypePNG; + } + if ([string isEqualToString:NSStringFromSLKPastableMediaType(SLKPastableMediaTypeJPEG)]) { + return SLKPastableMediaTypeJPEG; + } + if ([string isEqualToString:NSStringFromSLKPastableMediaType(SLKPastableMediaTypeTIFF)]) { + return SLKPastableMediaTypeTIFF; + } + if ([string isEqualToString:NSStringFromSLKPastableMediaType(SLKPastableMediaTypeGIF)]) { + return SLKPastableMediaTypeGIF; + } + if ([string isEqualToString:NSStringFromSLKPastableMediaType(SLKPastableMediaTypeMOV)]) { + return SLKPastableMediaTypeMOV; + } + if ([string isEqualToString:NSStringFromSLKPastableMediaType(SLKPastableMediaTypePassbook)]) { + return SLKPastableMediaTypePassbook; + } + if ([string isEqualToString:NSStringFromSLKPastableMediaType(SLKPastableMediaTypeImages)]) { + return SLKPastableMediaTypeImages; + } + return SLKPastableMediaTypeNone; +} + +- (BOOL)isExpanding +{ + if (self.numberOfLines >= self.maxNumberOfLines) { + return YES; + } + return NO; +} + +- (BOOL)slk_shouldHidePlaceholder +{ + if (self.placeholder.length == 0 || self.text.length > 0) { + return YES; + } + return NO; +} + +- (CGRect)slk_placeholderRectThatFits:(CGRect)bounds +{ + CGFloat padding = self.textContainer.lineFragmentPadding; + + CGRect rect = CGRectZero; + rect.size.height = [self.placeholderLabel sizeThatFits:bounds.size].height; + rect.size.width = self.textContainer.size.width - padding*2.0; + rect.origin = UIEdgeInsetsInsetRect(bounds, self.textContainerInset).origin; + rect.origin.x += padding; + + return rect; +} + + +#pragma mark - Setters + +- (void)setPlaceholder:(NSString *)placeholder +{ + self.placeholderLabel.text = placeholder; + self.accessibilityLabel = placeholder; + + [self setNeedsLayout]; +} + +- (void)setPlaceholderColor:(UIColor *)color +{ + self.placeholderLabel.textColor = color; +} + +- (void)setUndoManagerEnabled:(BOOL)enabled +{ + if (self.undoManagerEnabled == enabled) { + return; + } + + self.undoManager.levelsOfUndo = 10; + [self.undoManager removeAllActions]; + [self.undoManager setActionIsDiscardable:YES]; + + _undoManagerEnabled = enabled; +} + +- (void)setTypingSuggestionEnabled:(BOOL)enabled +{ + if (self.isTypingSuggestionEnabled == enabled) { + return; + } + + self.autocorrectionType = enabled ? UITextAutocorrectionTypeDefault : UITextAutocorrectionTypeNo; + self.spellCheckingType = enabled ? UITextSpellCheckingTypeDefault : UITextSpellCheckingTypeNo; + + [self refreshFirstResponder]; +} + + +#pragma mark - UITextView Overrides + +- (void)setSelectedRange:(NSRange)selectedRange +{ + [super setSelectedRange:selectedRange]; +} + +- (void)setSelectedTextRange:(UITextRange *)selectedTextRange +{ + [super setSelectedTextRange:selectedTextRange]; + + [[NSNotificationCenter defaultCenter] postNotificationName:SLKTextViewSelectedRangeDidChangeNotification object:self userInfo:nil]; +} + +- (void)setText:(NSString *)text +{ + // Registers for undo management + [self slk_prepareForUndo:@"Text Set"]; + + [super setText:text]; + + [[NSNotificationCenter defaultCenter] postNotificationName:UITextViewTextDidChangeNotification object:self]; +} + +- (void)setAttributedText:(NSAttributedString *)attributedText +{ + // Registers for undo management + [self slk_prepareForUndo:@"Attributed Text Set"]; + + [super setAttributedText:attributedText]; + + [[NSNotificationCenter defaultCenter] postNotificationName:UITextViewTextDidChangeNotification object:self]; +} + +- (void)setFont:(UIFont *)font +{ + NSString *contentSizeCategory = [[UIApplication sharedApplication] preferredContentSizeCategory]; + + [self setFontName:font.fontName pointSize:font.pointSize withContentSizeCategory:contentSizeCategory]; + + self.initialFontSize = font.pointSize; +} + +- (void)setFontName:(NSString *)fontName pointSize:(CGFloat)pointSize withContentSizeCategory:(NSString *)contentSizeCategory +{ + if (self.isDynamicTypeEnabled) { + pointSize += [SLKTextView pointSizeDifferenceForCategory:contentSizeCategory]; + } + + UIFont *dynamicFont = [UIFont fontWithName:fontName size:pointSize]; + + [super setFont:dynamicFont]; + + // Updates the placeholder font too + self.placeholderLabel.font = dynamicFont; +} + +- (void)setDynamicTypeEnabled:(BOOL)dynamicTypeEnabled +{ + if (self.isDynamicTypeEnabled == dynamicTypeEnabled) { + return; + } + + _dynamicTypeEnabled = dynamicTypeEnabled; + + NSString *contentSizeCategory = [[UIApplication sharedApplication] preferredContentSizeCategory]; + + [self setFontName:self.font.fontName pointSize:self.initialFontSize withContentSizeCategory:contentSizeCategory]; +} + +- (void)setTextAlignment:(NSTextAlignment)textAlignment +{ + [super setTextAlignment:textAlignment]; + + // Updates the placeholder text alignment too + self.placeholderLabel.textAlignment = textAlignment; +} + + +#pragma mark - UITextInput Overrides + +- (void)beginFloatingCursorAtPoint:(CGPoint)point +{ + [super beginFloatingCursorAtPoint:point]; + + _trackpadEnabled = YES; +} + +- (void)updateFloatingCursorAtPoint:(CGPoint)point +{ + [super updateFloatingCursorAtPoint:point]; +} + +- (void)endFloatingCursor +{ + [super endFloatingCursor]; + + _trackpadEnabled = NO; + + // We still need to notify a selection change in the textview after the trackpad is disabled + if (self.delegate && [self.delegate respondsToSelector:@selector(textViewDidChangeSelection:)]) { + [self.delegate textViewDidChangeSelection:self]; + } + + [[NSNotificationCenter defaultCenter] postNotificationName:SLKTextViewSelectedRangeDidChangeNotification object:self userInfo:nil]; +} + + +#pragma mark - UIResponder Overrides + +- (BOOL)canBecomeFirstResponder +{ + [self slk_addCustomMenuControllerItems]; + + return [super canBecomeFirstResponder]; +} + +- (BOOL)becomeFirstResponder +{ + return [super becomeFirstResponder]; +} + +- (BOOL)canResignFirstResponder +{ + // Removes undo/redo items + if (self.undoManagerEnabled) { + [self.undoManager removeAllActions]; + } + + return [super canResignFirstResponder]; +} + +- (BOOL)resignFirstResponder +{ + return [super resignFirstResponder]; +} + +- (BOOL)canPerformAction:(SEL)action withSender:(id)sender +{ + if (self.isFormatting) { + NSString *title = [self slk_formattingTitleFromSelector:action]; + NSString *symbol = [self slk_formattingSymbolWithTitle:title]; + + if (symbol.length > 0) { + if (self.delegate && [self.delegate respondsToSelector:@selector(textView:shouldOfferFormattingForSymbol:)]) { + return [self.delegate textView:self shouldOfferFormattingForSymbol:symbol]; + } + else { + return YES; + } + } + + return NO; + } + + if (action == @selector(delete:)) { + return NO; + } + + if (action == NSSelectorFromString(@"_share:") || action == NSSelectorFromString(@"_define:") || action == NSSelectorFromString(@"_promptForReplace:")) { + return NO; + } + + if (action == @selector(slk_presentFormattingMenu:)) { + return self.selectedRange.length > 0 ? YES : NO; + } + + if (action == @selector(paste:) && [self slk_isPasteboardItemSupported]) { + return YES; + } + + if (action == @selector(paste:) && [self slk_isPasteboardItemSupported]) { + return YES; + } + + if (self.undoManagerEnabled) { + if (action == @selector(slk_undo:)) { + if (self.undoManager.undoActionIsDiscardable) { + return NO; + } + return [self.undoManager canUndo]; + } + if (action == @selector(slk_redo:)) { + if (self.undoManager.redoActionIsDiscardable) { + return NO; + } + return [self.undoManager canRedo]; + } + } + + return [super canPerformAction:action withSender:sender]; +} + +- (void)paste:(id)sender +{ + id pastedItem = [self slk_pastedItem]; + + if ([pastedItem isKindOfClass:[NSDictionary class]]) { + [[NSNotificationCenter defaultCenter] postNotificationName:SLKTextViewDidPasteItemNotification object:nil userInfo:pastedItem]; + } + else if ([pastedItem isKindOfClass:[NSString class]]) { + // Respect the delegate yo! + if (self.delegate && [self.delegate respondsToSelector:@selector(textView:shouldChangeTextInRange:replacementText:)]) { + if (![self.delegate textView:self shouldChangeTextInRange:self.selectedRange replacementText:pastedItem]) { + return; + } + } + + // Inserting the text fixes a UITextView bug whitch automatically scrolls to the bottom + // and beyond scroll content size sometimes when the text is too long + [self slk_insertTextAtCaretRange:pastedItem]; + } +} + + +#pragma mark - NSObject Overrides + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel +{ + if ([super methodSignatureForSelector:sel]) { + return [super methodSignatureForSelector:sel]; + } + return [super methodSignatureForSelector:@selector(slk_format:)]; +} + +- (void)forwardInvocation:(NSInvocation *)invocation +{ + NSString *title = [self slk_formattingTitleFromSelector:[invocation selector]]; + + if (title.length > 0) { + [self slk_format:title]; + } + else { + [super forwardInvocation:invocation]; + } +} + + +#pragma mark - Custom Actions + +- (void)slk_flashScrollIndicatorsIfNeeded +{ + if (self.numberOfLines == self.maxNumberOfLines+1) { + if (!_didFlashScrollIndicators) { + _didFlashScrollIndicators = YES; + [super flashScrollIndicators]; + } + } + else if (_didFlashScrollIndicators) { + _didFlashScrollIndicators = NO; + } +} + +- (void)refreshFirstResponder +{ + if (!self.isFirstResponder) { + return; + } + + _didNotResignFirstResponder = YES; + [self resignFirstResponder]; + + _didNotResignFirstResponder = NO; + [self becomeFirstResponder]; +} + +- (void)refreshInputViews +{ + _didNotResignFirstResponder = YES; + + [super reloadInputViews]; + + _didNotResignFirstResponder = NO; +} + +- (void)slk_addCustomMenuControllerItems +{ + UIMenuItem *undo = [[UIMenuItem alloc] initWithTitle:NSLocalizedString(@"Undo", nil) action:@selector(slk_undo:)]; + UIMenuItem *redo = [[UIMenuItem alloc] initWithTitle:NSLocalizedString(@"Redo", nil) action:@selector(slk_redo:)]; + UIMenuItem *format = [[UIMenuItem alloc] initWithTitle:NSLocalizedString(@"Format", nil) action:@selector(slk_presentFormattingMenu:)]; + + [[UIMenuController sharedMenuController] setMenuItems:@[undo, redo, format]]; +} + +- (void)slk_undo:(id)sender +{ + [self.undoManager undo]; +} + +- (void)slk_redo:(id)sender +{ + [self.undoManager redo]; +} + +- (void)slk_presentFormattingMenu:(id)sender +{ + NSMutableArray *items = [NSMutableArray arrayWithCapacity:self.registeredFormattingTitles.count]; + + for (NSString *name in self.registeredFormattingTitles) { + + NSString *sel = [NSString stringWithFormat:@"%@%@", SLKTextViewGenericFormattingSelectorPrefix, name]; + + UIMenuItem *item = [[UIMenuItem alloc] initWithTitle:name action:NSSelectorFromString(sel)]; + [items addObject:item]; + } + + self.formatting = YES; + + UIMenuController *menu = [UIMenuController sharedMenuController]; + [menu setMenuItems:items]; + + NSLayoutManager *manager = self.layoutManager; + CGRect targetRect = [manager boundingRectForGlyphRange:self.selectedRange inTextContainer:self.textContainer]; + + [menu setTargetRect:targetRect inView:self]; + + [menu setMenuVisible:YES animated:YES]; +} + +- (NSString *)slk_formattingTitleFromSelector:(SEL)selector +{ + NSString *selectorString = NSStringFromSelector(selector); + NSRange match = [selectorString rangeOfString:SLKTextViewGenericFormattingSelectorPrefix]; + + if (match.location != NSNotFound) { + return [selectorString substringFromIndex:SLKTextViewGenericFormattingSelectorPrefix.length]; + } + + return nil; +} + +- (NSString *)slk_formattingSymbolWithTitle:(NSString *)title +{ + NSUInteger idx = [self.registeredFormattingTitles indexOfObject:title]; + + if (idx <= self.registeredFormattingSymbols.count -1) { + return self.registeredFormattingSymbols[idx]; + } + + return nil; +} + +- (void)slk_format:(NSString *)titles +{ + NSString *symbol = [self slk_formattingSymbolWithTitle:titles]; + + if (symbol.length > 0) { + NSRange selection = self.selectedRange; + + NSRange range = [self slk_insertText:symbol inRange:NSMakeRange(selection.location, 0)]; + range.location += selection.length; + range.length = 0; + + // The default behavior is to add a closure + BOOL addClosure = YES; + + if (self.delegate && [self.delegate respondsToSelector:@selector(textView:shouldInsertSuffixForFormattingWithSymbol:prefixRange:)]) { + addClosure = [self.delegate textView:self shouldInsertSuffixForFormattingWithSymbol:symbol prefixRange:selection]; + } + + if (addClosure) { + self.selectedRange = [self slk_insertText:symbol inRange:range]; + } + } +} + + +#pragma mark - Markdown Formatting + +- (void)registerMarkdownFormattingSymbol:(NSString *)symbol withTitle:(NSString *)title +{ + if (!symbol || !title) { + return; + } + + if (!_registeredFormattingTitles) { + _registeredFormattingTitles = [NSMutableArray new]; + _registeredFormattingSymbols = [NSMutableArray new]; + } + + // Adds the symbol if not contained already + if (![self.registeredSymbols containsObject:symbol]) { + [self.registeredFormattingTitles addObject:title]; + [self.registeredFormattingSymbols addObject:symbol]; + } +} + +- (NSArray *)registeredSymbols +{ + return self.registeredFormattingSymbols; +} + + +#pragma mark - Notification Events + +- (void)slk_didBeginEditing:(NSNotification *)notification +{ + if (![notification.object isEqual:self]) { + return; + } + + // Do something +} + +- (void)slk_didChangeText:(NSNotification *)notification +{ + if (![notification.object isEqual:self]) { + return; + } + + if (self.placeholderLabel.hidden != [self slk_shouldHidePlaceholder]) { + [self setNeedsLayout]; + } + + [self slk_flashScrollIndicatorsIfNeeded]; +} + +- (void)slk_didEndEditing:(NSNotification *)notification +{ + if (![notification.object isEqual:self]) { + return; + } + + // Do something +} + +- (void)slk_didChangeTextInputMode:(NSNotification *)notification +{ + // Do something +} + +- (void)slk_didChangeContentSizeCategory:(NSNotification *)notification +{ + if (!self.isDynamicTypeEnabled) { + return; + } + + NSString *contentSizeCategory = notification.userInfo[UIContentSizeCategoryNewValueKey]; + + [self setFontName:self.font.fontName pointSize:self.initialFontSize withContentSizeCategory:contentSizeCategory]; + + NSString *text = [self.text copy]; + + // Reloads the content size of the text view + [self setText:@" "]; + [self setText:text]; +} + +- (void)slk_willShowMenuController:(NSNotification *)notification +{ + +} + +- (void)slk_didHideMenuController:(NSNotification *)notification +{ + self.formatting = NO; + + [self slk_addCustomMenuControllerItems]; +} + + +#pragma mark - KVO Listener + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +{ + if ([object isEqual:self] && [keyPath isEqualToString:NSStringFromSelector(@selector(contentSize))]) { + [[NSNotificationCenter defaultCenter] postNotificationName:SLKTextViewContentSizeDidChangeNotification object:self userInfo:nil]; + } + else { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + } +} + + +#pragma mark - Motion Events + +- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event +{ + if (event.type == UIEventTypeMotion && event.subtype == UIEventSubtypeMotionShake) { + [[NSNotificationCenter defaultCenter] postNotificationName:SLKTextViewDidShakeNotification object:self]; + } +} + + +#pragma mark - External Keyboard Support + +- (NSArray *)keyCommands +{ + if (_keyboardCommands) { + return _keyboardCommands; + } + + _keyboardCommands = @[ + // Return + [UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:UIKeyModifierShift action:@selector(slk_didPressLineBreakKeys:)], + [UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:UIKeyModifierAlternate action:@selector(slk_didPressLineBreakKeys:)], + [UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:UIKeyModifierControl action:@selector(slk_didPressLineBreakKeys:)], + + // Undo/Redo + [UIKeyCommand keyCommandWithInput:@"z" modifierFlags:UIKeyModifierCommand action:@selector(slk_didPressCommandZKeys:)], + [UIKeyCommand keyCommandWithInput:@"z" modifierFlags:UIKeyModifierShift|UIKeyModifierCommand action:@selector(slk_didPressCommandZKeys:)], + ]; + + return _keyboardCommands; +} + + +#pragma mark Line Break + +- (void)slk_didPressLineBreakKeys:(id)sender +{ + [self slk_insertNewLineBreak]; +} + + +#pragma mark Undo/Redo Text + +- (void)slk_didPressCommandZKeys:(id)sender +{ + if (!self.undoManagerEnabled) { + return; + } + + UIKeyCommand *keyCommand = (UIKeyCommand *)sender; + + if ((keyCommand.modifierFlags & UIKeyModifierShift) > 0) { + + if ([self.undoManager canRedo]) { + [self.undoManager redo]; + } + } + else { + if ([self.undoManager canUndo]) { + [self.undoManager undo]; + } + } +} + +#pragma mark Up/Down Cursor Movement + +- (void)didPressAnyArrowKey:(id)sender +{ + if (self.text.length == 0 || self.numberOfLines < 2) { + return; + } + + UIKeyCommand *keyCommand = (UIKeyCommand *)sender; + + if ([keyCommand.input isEqualToString:UIKeyInputUpArrow]) { + [self slk_moveCursorTodirection:UITextLayoutDirectionUp]; + } + else if ([keyCommand.input isEqualToString:UIKeyInputDownArrow]) { + [self slk_moveCursorTodirection:UITextLayoutDirectionDown]; + } +} + +- (void)slk_moveCursorTodirection:(UITextLayoutDirection)direction +{ + UITextPosition *start = (direction == UITextLayoutDirectionUp) ? self.selectedTextRange.start : self.selectedTextRange.end; + + if ([self slk_isNewVerticalMovementForPosition:start inDirection:direction]) { + self.verticalMoveDirection = direction; + self.verticalMoveStartCaretRect = [self caretRectForPosition:start]; + } + + if (start) { + UITextPosition *end = [self slk_closestPositionToPosition:start inDirection:direction]; + + if (end) { + self.verticalMoveLastCaretRect = [self caretRectForPosition:end]; + self.selectedTextRange = [self textRangeFromPosition:end toPosition:end]; + + [self slk_scrollToCaretPositonAnimated:NO]; + } + } +} + +// Based on code from Ruben Cabaco +// https://gist.github.com/rcabaco/6765778 + +- (UITextPosition *)slk_closestPositionToPosition:(UITextPosition *)position inDirection:(UITextLayoutDirection)direction +{ + // Only up/down are implemented. No real need for left/right since that is native to UITextInput. + NSParameterAssert(direction == UITextLayoutDirectionUp || direction == UITextLayoutDirectionDown); + + // Translate the vertical direction to a horizontal direction. + UITextLayoutDirection lookupDirection = (direction == UITextLayoutDirectionUp) ? UITextLayoutDirectionLeft : UITextLayoutDirectionRight; + + // Walk one character at a time in `lookupDirection` until the next line is reached. + UITextPosition *checkPosition = position; + UITextPosition *closestPosition = position; + CGRect startingCaretRect = [self caretRectForPosition:position]; + CGRect nextLineCaretRect; + BOOL isInNextLine = NO; + + while (YES) { + UITextPosition *nextPosition = [self positionFromPosition:checkPosition inDirection:lookupDirection offset:1]; + + // End of line. + if (!nextPosition || [self comparePosition:checkPosition toPosition:nextPosition] == NSOrderedSame) { + break; + } + + checkPosition = nextPosition; + CGRect checkRect = [self caretRectForPosition:checkPosition]; + if (CGRectGetMidY(startingCaretRect) != CGRectGetMidY(checkRect)) { + // While on the next line stop just above/below the starting position. + if (lookupDirection == UITextLayoutDirectionLeft && CGRectGetMidX(checkRect) <= CGRectGetMidX(self.verticalMoveStartCaretRect)) { + closestPosition = checkPosition; + break; + } + if (lookupDirection == UITextLayoutDirectionRight && CGRectGetMidX(checkRect) >= CGRectGetMidX(self.verticalMoveStartCaretRect)) { + closestPosition = checkPosition; + break; + } + // But don't skip lines. + if (isInNextLine && CGRectGetMidY(checkRect) != CGRectGetMidY(nextLineCaretRect)) { + break; + } + + isInNextLine = YES; + nextLineCaretRect = checkRect; + closestPosition = checkPosition; + } + } + return closestPosition; +} + +- (BOOL)slk_isNewVerticalMovementForPosition:(UITextPosition *)position inDirection:(UITextLayoutDirection)direction +{ + CGRect caretRect = [self caretRectForPosition:position]; + BOOL noPreviousStartPosition = CGRectEqualToRect(self.verticalMoveStartCaretRect, CGRectZero); + BOOL caretMovedSinceLastPosition = !CGRectEqualToRect(caretRect, self.verticalMoveLastCaretRect); + BOOL directionChanged = self.verticalMoveDirection != direction; + + BOOL newMovement = noPreviousStartPosition || caretMovedSinceLastPosition || directionChanged; + return newMovement; +} + + +#pragma mark - NSNotificationCenter register/unregister + +- (void)slk_registerNotifications +{ + [self slk_unregisterNotifications]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(slk_didBeginEditing:) name:UITextViewTextDidBeginEditingNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(slk_didChangeText:) name:UITextViewTextDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(slk_didEndEditing:) name:UITextViewTextDidEndEditingNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(slk_didChangeTextInputMode:) name:UITextInputCurrentInputModeDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(slk_didChangeContentSizeCategory:) name:UIContentSizeCategoryDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(slk_willShowMenuController:) name:UIMenuControllerWillShowMenuNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(slk_didHideMenuController:) name:UIMenuControllerDidHideMenuNotification object:nil]; +} + +- (void)slk_unregisterNotifications +{ + [[NSNotificationCenter defaultCenter] removeObserver:self name:UITextViewTextDidBeginEditingNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UITextViewTextDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UITextViewTextDidEndEditingNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UITextInputCurrentInputModeDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIContentSizeCategoryDidChangeNotification object:nil]; +} + + +#pragma mark - Lifeterm + +- (void)dealloc +{ + [self slk_unregisterNotifications]; + + [self removeObserver:self forKeyPath:NSStringFromSelector(@selector(contentSize))]; + + _placeholderLabel = nil; +} + +@end
http://git-wip-us.apache.org/repos/asf/usergrid/blob/7442c881/sdks/swift/Samples/ActivityFeed/Pods/SlackTextViewController/Source/SLKTextViewController.h ---------------------------------------------------------------------- diff --git a/sdks/swift/Samples/ActivityFeed/Pods/SlackTextViewController/Source/SLKTextViewController.h b/sdks/swift/Samples/ActivityFeed/Pods/SlackTextViewController/Source/SLKTextViewController.h new file mode 100644 index 0000000..1e429d3 --- /dev/null +++ b/sdks/swift/Samples/ActivityFeed/Pods/SlackTextViewController/Source/SLKTextViewController.h @@ -0,0 +1,584 @@ +// +// Copyright 2014 Slack Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import <Foundation/Foundation.h> +#import <UIKit/UIKit.h> + +#import "SLKTextInputbar.h" +#import "SLKTextView.h" +#import "SLKTypingIndicatorView.h" +#import "SLKTypingIndicatorProtocol.h" + +#import "SLKTextView+SLKAdditions.h" +#import "UIScrollView+SLKAdditions.h" +#import "UIView+SLKAdditions.h" + +#import "SLKUIConstants.h" + +/** + UIKeyboard notification replacement, posting reliably only when showing/hiding the keyboard (not when resizing keyboard, or with inputAccessoryView reloads, etc). + Only triggered when using SLKTextViewController's text view. + */ +UIKIT_EXTERN NSString *const SLKKeyboardWillShowNotification; +UIKIT_EXTERN NSString *const SLKKeyboardDidShowNotification; +UIKIT_EXTERN NSString *const SLKKeyboardWillHideNotification; +UIKIT_EXTERN NSString *const SLKKeyboardDidHideNotification; + +/** + This feature doesn't work on iOS 9 due to no legit alternatives to detect the keyboard view. + Open Radar: http://openradar.appspot.com/radar?id=5021485877952512 + */ +UIKIT_EXTERN NSString *const SLKTextInputbarDidMoveNotification; + +typedef NS_ENUM(NSUInteger, SLKKeyboardStatus) { + SLKKeyboardStatusDidHide, + SLKKeyboardStatusWillShow, + SLKKeyboardStatusDidShow, + SLKKeyboardStatusWillHide +}; + +/** @name A drop-in UIViewController subclass with a growing text input view and other useful messaging features. */ +NS_CLASS_AVAILABLE_IOS(7_0) @interface SLKTextViewController : UIViewController <SLKTextViewDelegate, UITableViewDelegate, UITableViewDataSource, + UICollectionViewDelegate, UICollectionViewDataSource, + UIGestureRecognizerDelegate, UIAlertViewDelegate> + +/** The main table view managed by the controller object. Created by default initializing with -init or initWithNibName:bundle: */ +@property (nonatomic, readonly) UITableView *tableView; + +/** The main collection view managed by the controller object. Not nil if the controller is initialised with -initWithCollectionViewLayout: */ +@property (nonatomic, readonly) UICollectionView *collectionView; + +/** The main scroll view managed by the controller object. Not nil if the controller is initialised with -initWithScrollView: */ +@property (nonatomic, readonly) UIScrollView *scrollView; + +/** The bottom toolbar containing a text view and buttons. */ +@property (nonatomic, readonly) SLKTextInputbar *textInputbar; + +/** The default typing indicator used to display user names horizontally. */ +@property (nonatomic, readonly) SLKTypingIndicatorView *typingIndicatorView; + +/** + The custom typing indicator view. Default is kind of SLKTypingIndicatorView. + To customize the typing indicator view, you will need to call -registerClassForTypingIndicatorView: nside of any initialization method. + To interact with it directly, you will need to cast the return value of -typingIndicatorProxyView to the appropriate type. + */ +@property (nonatomic, readonly) UIView <SLKTypingIndicatorProtocol> *typingIndicatorProxyView; + +/** A single tap gesture used to dismiss the keyboard. SLKTextViewController is its delegate. */ +@property (nonatomic, readonly) UIGestureRecognizer *singleTapGesture; + +/** A vertical pan gesture used for bringing the keyboard from the bottom. SLKTextViewController is its delegate. */ +@property (nonatomic, readonly) UIPanGestureRecognizer *verticalPanGesture; + +/** YES if control's animation should have bouncy effects. Default is YES. */ +@property (nonatomic, assign) BOOL bounces; + +/** YES if text view's content can be cleaned with a shake gesture. Default is NO. */ +@property (nonatomic, assign) BOOL shakeToClearEnabled; + +/** + YES if keyboard can be dismissed gradually with a vertical panning gesture. Default is YES. + + This feature doesn't work on iOS 9 due to no legit alternatives to detect the keyboard view. + Open Radar: http://openradar.appspot.com/radar?id=5021485877952512 + */ +@property (nonatomic, assign, getter = isKeyboardPanningEnabled) BOOL keyboardPanningEnabled; + +/** YES if an external keyboard has been detected (this value updates only when the text view becomes first responder). */ +@property (nonatomic, readonly, getter=isExternalKeyboardDetected) BOOL externalKeyboardDetected; + +/** YES if the keyboard has been detected as undocked or split (iPad Only). */ +@property (nonatomic, readonly, getter=isKeyboardUndocked) BOOL keyboardUndocked; + +/** YES if after right button press, the text view is cleared out. Default is YES. */ +@property (nonatomic, assign) BOOL shouldClearTextAtRightButtonPress; + +/** YES if the scrollView should scroll to bottom when the keyboard is shown. Default is NO.*/ +@property (nonatomic, assign) BOOL shouldScrollToBottomAfterKeyboardShows; + +/** + YES if the main table view is inverted. Default is YES. + This allows the table view to start from the bottom like any typical messaging interface. + If inverted, you must assign the same transform property to your cells to match the orientation (ie: cell.transform = tableView.transform;) + Inverting the table view will enable some great features such as content offset corrections automatically when resizing the text input and/or showing autocompletion. + */ +@property (nonatomic, assign, getter = isInverted) BOOL inverted; + +/** YES if the view controller is presented inside of a popover controller. If YES, the keyboard won't move the text input bar and tapping on the tableView/collectionView will not cause the keyboard to be dismissed. This property is compatible only with iPad. */ +@property (nonatomic, assign, getter = isPresentedInPopover) BOOL presentedInPopover; + +/** Convenience accessors (accessed through the text input bar) */ +@property (nonatomic, readonly) SLKTextView *textView; +@property (nonatomic, readonly) UIButton *leftButton; +@property (nonatomic, readonly) UIButton *rightButton; + + +#pragma mark - Initialization +///------------------------------------------------ +/// @name Initialization +///------------------------------------------------ + +/** + Initializes a text view controller to manage a table view of a given style. + If you use the standard -init method, a table view with plain style will be created. + + @param style A constant that specifies the style of main table view that the controller object is to manage (UITableViewStylePlain or UITableViewStyleGrouped). + @return An initialized SLKTextViewController object or nil if the object could not be created. + */ +- (instancetype)initWithTableViewStyle:(UITableViewStyle)style SLK_DESIGNATED_INITIALIZER; + +/** + Initializes a collection view controller and configures the collection view with the provided layout. + If you use the standard -init method, a table view with plain style will be created. + + @param layout The layout object to associate with the collection view. The layout controls how the collection view presents its cells and supplementary views. + @return An initialized SLKTextViewController object or nil if the object could not be created. + */ +- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout SLK_DESIGNATED_INITIALIZER; + +/** + Initializes a text view controller to manage an arbitraty scroll view. The caller is responsible for configuration of the scroll view, including wiring the delegate. + + @param a UISCrollView to be used as the main content area. + @return An initialized SLKTextViewController object or nil if the object could not be created. + */ +- (instancetype)initWithScrollView:(UIScrollView *)scrollView SLK_DESIGNATED_INITIALIZER; + +/** + Initializes either a table or collection view controller. + You must override either +tableViewStyleForCoder: or +collectionViewLayoutForCoder: to define witch view to be layed out. + + @param decoder An unarchiver object. + @return An initialized SLKTextViewController object or nil if the object could not be created. + */ +- (instancetype)initWithCoder:(NSCoder *)decoder SLK_DESIGNATED_INITIALIZER; + +/** + Returns the tableView style to be configured when using Interface Builder. Default is UITableViewStylePlain. + You must override this method if you want to configure a tableView. + + @param decoder An unarchiver object. + @return The tableView style to be used in the new instantiated tableView. + */ ++ (UITableViewStyle)tableViewStyleForCoder:(NSCoder *)decoder; + +/** + Returns the tableView style to be configured when using Interface Builder. Default is nil. + You must override this method if you want to configure a collectionView. + + @param decoder An unarchiver object. + @return The collectionView style to be used in the new instantiated collectionView. + */ ++ (UICollectionViewLayout *)collectionViewLayoutForCoder:(NSCoder *)decoder; + + +#pragma mark - Keyboard Handling +///------------------------------------------------ +/// @name Keyboard Handling +///------------------------------------------------ + +/** + Presents the keyboard, if not already, animated. + You can override this method to perform additional tasks associated with presenting the keyboard. + You SHOULD call super to inherit some conditionals. + + @param animated YES if the keyboard should show using an animation. + */ +- (void)presentKeyboard:(BOOL)animated; + +/** + Dimisses the keyboard, if not already, animated. + You can override this method to perform additional tasks associated with dismissing the keyboard. + You SHOULD call super to inherit some conditionals. + + @param animated YES if the keyboard should be dismissed using an animation. + */ +- (void)dismissKeyboard:(BOOL)animated; + +/** + Verifies if the text input bar should still move up/down even if it is not first responder. Default is NO. + You can override this method to perform additional tasks associated with presenting the view. + You don't need call super since this method doesn't do anything. + + @param responder The current first responder object. + @return YES so the text input bar still move up/down. + */ +- (BOOL)forceTextInputbarAdjustmentForResponder:(UIResponder *)responder; + +/** + Verifies if the text input bar should still move up/down when it is first responder. Default is NO. + This is very useful when presenting the view controller in a custom modal presentation, when there keyboard events are being handled externally to reframe the presented view. + You SHOULD call super to inherit some conditionals. + */ +- (BOOL)ignoreTextInputbarAdjustment NS_REQUIRES_SUPER; + +/** + Notifies the view controller that the keyboard changed status. + You can override this method to perform additional tasks associated with presenting the view. + You don't need call super since this method doesn't do anything. + + @param status The new keyboard status. + */ +- (void)didChangeKeyboardStatus:(SLKKeyboardStatus)status; + + +#pragma mark - Interaction Notifications +///------------------------------------------------ +/// @name Interaction Notifications +///------------------------------------------------ + +/** + Notifies the view controller that the text will update. + You can override this method to perform additional tasks associated with text changes. + You MUST call super at some point in your implementation. + */ +- (void)textWillUpdate NS_REQUIRES_SUPER; + +/** + Notifies the view controller that the text did update. + You can override this method to perform additional tasks associated with text changes. + You MUST call super at some point in your implementation. + + @param If YES, the text input bar will be resized using an animation. + */ +- (void)textDidUpdate:(BOOL)animated NS_REQUIRES_SUPER; + +/** + Notifies the view controller that the text selection did change. + Use this method a replacement of UITextViewDelegate's -textViewDidChangeSelection: which is not reliable enough when using third-party keyboards (they don't forward events properly sometimes). + + You can override this method to perform additional tasks associated with text changes. + You MUST call super at some point in your implementation. + */ +- (void)textSelectionDidChange NS_REQUIRES_SUPER; + +/** + Notifies the view controller when the left button's action has been triggered, manually. + You can override this method to perform additional tasks associated with the left button. + You don't need call super since this method doesn't do anything. + + @param sender The object calling this method. + */ +- (void)didPressLeftButton:(id)sender; + +/** + Notifies the view controller when the right button's action has been triggered, manually or by using the keyboard return key. + You can override this method to perform additional tasks associated with the right button. + You MUST call super at some point in your implementation. + + @param sender The object calling this method. + */ +- (void)didPressRightButton:(id)sender NS_REQUIRES_SUPER; + +/** + Verifies if the right button can be pressed. If NO, the button is disabled. + You can override this method to perform additional tasks. You SHOULD call super to inherit some conditionals. + + @return YES if the right button can be pressed. + */ +- (BOOL)canPressRightButton; + +/** + Notifies the view controller when the user has pasted a supported media content (images and/or videos). + You can override this method to perform additional tasks associated with image/video pasting. You don't need to call super since this method doesn't do anything. + Only supported pastable medias configured in SLKTextView will be forwarded (take a look at SLKPastableMediaType). + + @para userInfo The payload containing the media data, content and media types. + */ +- (void)didPasteMediaContent:(NSDictionary *)userInfo; + +/** + Verifies that the typing indicator view should be shown. Default is YES, if meeting some requierements. + You can override this method to perform additional tasks. + You SHOULD call super to inherit some conditionals. + + @return YES if the typing indicator view should be presented. + */ +- (BOOL)canShowTypingIndicator; + +/** + Notifies the view controller when the user has shaked the device for undoing text typing. + You can override this method to perform additional tasks associated with the shake gesture. + Calling super will prompt a system alert view with undo option. This will not be called if 'undoShakingEnabled' is set to NO and/or if the text view's content is empty. + */ +- (void)willRequestUndo; + +/** + Notifies the view controller when the user has pressed the Return key (âµ) with an external keyboard. + You can override this method to perform additional tasks. + You MUST call super at some point in your implementation. + */ +- (void)didPressReturnKey:(id)sender NS_REQUIRES_SUPER; + +/** + Notifies the view controller when the user has pressed the Escape key (Esc) with an external keyboard. + You can override this method to perform additional tasks. + You MUST call super at some point in your implementation. + */ +- (void)didPressEscapeKey:(id)sender NS_REQUIRES_SUPER; + +/** + Notifies the view controller when the user has pressed the arrow key with an external keyboard. + You can override this method to perform additional tasks. + You MUST call super at some point in your implementation. + */ +- (void)didPressArrowKey:(id)sender NS_REQUIRES_SUPER; + + +#pragma mark - Text Input Bar Adjustment +///------------------------------------------------ +/// @name Text Input Bar Adjustment +///------------------------------------------------ + +/** YES if the text inputbar is hidden. Default is NO. */ +@property (nonatomic, getter=isTextInputbarHidden) BOOL textInputbarHidden; + +/** + Changes the visibility of the text input bar. + Calling this method with the animated parameter set to NO is equivalent to setting the value of the toolbarHidden property directly. + + @param hidden Specify YES to hide the toolbar or NO to show it. + @param animated Specify YES if you want the toolbar to be animated on or off the screen. + */ +- (void)setTextInputbarHidden:(BOOL)hidden animated:(BOOL)animated; + + +#pragma mark - Text Edition +///------------------------------------------------ +/// @name Text Edition +///------------------------------------------------ + +/** YES if the text editing mode is active. */ +@property (nonatomic, readonly, getter = isEditing) BOOL editing; + +/** + Re-uses the text layout for edition, displaying an accessory view on top of the text input bar with options (cancel & save). + You can override this method to perform additional tasks + You MUST call super at some point in your implementation. + + @param text The string text to edit. + */ +- (void)editText:(NSString *)text NS_REQUIRES_SUPER; + +/** + Notifies the view controller when the editing bar's right button's action has been triggered, manually or by using the external keyboard's Return key. + You can override this method to perform additional tasks associated with accepting changes. + You MUST call super at some point in your implementation. + + @param sender The object calling this method. + */ +- (void)didCommitTextEditing:(id)sender NS_REQUIRES_SUPER; + +/** + Notifies the view controller when the editing bar's right button's action has been triggered, manually or by using the external keyboard's Esc key. + You can override this method to perform additional tasks associated with accepting changes. + You MUST call super at some point in your implementation. + + @param sender The object calling this method. + */ +- (void)didCancelTextEditing:(id)sender NS_REQUIRES_SUPER; + + +#pragma mark - Text Auto-Completion +///------------------------------------------------ +/// @name Text Auto-Completion +///------------------------------------------------ + +/** The table view used to display autocompletion results. */ +@property (nonatomic, readonly) UITableView *autoCompletionView; + +/** YES if the autocompletion mode is active. */ +@property (nonatomic, readonly, getter = isAutoCompleting) BOOL autoCompleting; + +/** The recently found prefix symbol used as prefix for autocompletion mode. */ +@property (nonatomic, readonly, copy) NSString *foundPrefix; + +/** The range of the found prefix in the text view content. */ +@property (nonatomic, readonly) NSRange foundPrefixRange; + +/** The recently found word at the text view's caret position. */ +@property (nonatomic, readonly, copy) NSString *foundWord; + +/** An array containing all the registered prefix strings for autocompletion. */ +@property (nonatomic, readonly, copy) NSArray *registeredPrefixes; + +/** + Registers any string prefix for autocompletion detection, useful for user mentions and/or hashtags autocompletion. + The prefix must be valid string (i.e: '@', '#', '\', and so on). This also checks if no repeated prefix are inserted. + Prefixes can be of any length. + + @param prefixes An array of prefix strings. + */ +- (void)registerPrefixesForAutoCompletion:(NSArray *)prefixes; + +/** + Notifies the view controller either the autocompletion prefix or word have changed. + Use this method to modify your data source or fetch data asynchronously from an HTTP resource. + Once your data source is ready, make sure to call -showAutoCompletionView: to display the view accordingly. + You don't need call super since this method doesn't do anything. + + @param prefix The detected prefix. + @param word The derected word. + */ +- (void)didChangeAutoCompletionPrefix:(NSString *)prefix andWord:(NSString *)word; + +/** + Use this method to programatically show/hide the autocompletion view. + Right before the view is shown, -reloadData is called. So avoid calling it manually. + + @param show YES if the autocompletion view should be shown. + */ +- (void)showAutoCompletionView:(BOOL)show; + +/** + Verifies that the autocompletion view should be shown. Default is NO. + To enabled autocompletion, you MUST override this method to perform additional tasks, before the autocompletion view is shown (i.e. populating the data source). + + @return YES if the autocompletion view should be shown. + */ +- (BOOL)canShowAutoCompletion DEPRECATED_MSG_ATTRIBUTE("Override -didChangeAutoCompletionPrefix:andWord: instead"); + +/** + Returns a custom height for the autocompletion view. Default is 0.0. + You can override this method to return a custom height. + + @return The autocompletion view's height. + */ +- (CGFloat)heightForAutoCompletionView; + +/** + Returns the maximum height for the autocompletion view. Default is 140 pts. + You can override this method to return a custom max height. + + @return The autocompletion view's max height. + */ +- (CGFloat)maximumHeightForAutoCompletionView; + +/** + Cancels and hides the autocompletion view, animated. + */ +- (void)cancelAutoCompletion; + +/** + Accepts the autocompletion, replacing the detected word with a new string, keeping the prefix. + This method is a convinience of -acceptAutoCompletionWithString:keepPrefix: + + @param string The string to be used for replacing autocompletion placeholders. + */ +- (void)acceptAutoCompletionWithString:(NSString *)string; + +/** + Accepts the autocompletion, replacing the detected word with a new string, and optionally replacing the prefix too. + + @param string The string to be used for replacing autocompletion placeholders. + @param keepPrefix YES if the prefix shouldn't be overidden. + */ +- (void)acceptAutoCompletionWithString:(NSString *)string keepPrefix:(BOOL)keepPrefix; + + +#pragma mark - Text Caching +///------------------------------------------------ +/// @name Text Caching +///------------------------------------------------ + +/** + Returns the key to be associated with a given text to be cached. Default is nil. + To enable text caching, you must override this method to return valid key. + The text view will be populated automatically when the view controller is configured. + You don't need to call super since this method doesn't do anything. + + @return The string key for which to enable text caching. + */ +- (NSString *)keyForTextCaching; + +/** + Removes the current's vien controller cached text. + To enable this, you must return a valid key string in -keyForTextCaching. + */ +- (void)clearCachedText; + +/** + Removes all the cached text from disk. + */ ++ (void)clearAllCachedText; + + +#pragma mark - Customization +///------------------------------------------------ +/// @name Customization +///------------------------------------------------ + +/** + Registers a class for customizing the behavior and appearance of the text view. + You need to call this method inside of any initialization method. + + @param aClass A SLKTextView subclass. + */ +- (void)registerClassForTextView:(Class)aClass; + +/** + Registers a class for customizing the behavior and appearance of the typing indicator view. + You need to call this method inside of any initialization method. + Make sure to conform to SLKTypingIndicatorProtocol and implement the required methods. + + @param aClass A UIView subclass conforming to the SLKTypingIndicatorProtocol. + */ +- (void)registerClassForTypingIndicatorView:(Class)aClass; + + +#pragma mark - Delegate Methods Requiring Super +///------------------------------------------------ +/// @name Delegate Methods Requiring Super +///------------------------------------------------ + +/** UITextViewDelegate */ +- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text NS_REQUIRES_SUPER; + +/** SLKTextViewDelegate */ +- (BOOL)textView:(SLKTextView *)textView shouldInsertSuffixForFormattingWithSymbol:(NSString *)symbol prefixRange:(NSRange)prefixRange NS_REQUIRES_SUPER; + +/** UIScrollViewDelegate */ +- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView NS_REQUIRES_SUPER; +- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate NS_REQUIRES_SUPER; +- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView NS_REQUIRES_SUPER; +- (void)scrollViewDidScroll:(UIScrollView *)scrollView NS_REQUIRES_SUPER; + +/** UIGestureRecognizerDelegate */ +- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer NS_REQUIRES_SUPER; + +/** UIAlertViewDelegate */ +#ifndef __IPHONE_8_0 +- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex NS_REQUIRES_SUPER; +#endif + +#pragma mark - Life Cycle Methods Requiring Super +///------------------------------------------------ +/// @name Life Cycle Methods Requiring Super +///------------------------------------------------ + +/** + Configures view hierarchy and layout constraints. If you override these methods, make sure to call super. + */ +- (void)loadView NS_REQUIRES_SUPER; +- (void)viewDidLoad NS_REQUIRES_SUPER; +- (void)viewWillAppear:(BOOL)animated NS_REQUIRES_SUPER; +- (void)viewDidAppear:(BOOL)animated NS_REQUIRES_SUPER; +- (void)viewWillDisappear:(BOOL)animated NS_REQUIRES_SUPER; +- (void)viewDidDisappear:(BOOL)animated NS_REQUIRES_SUPER; +- (void)viewWillLayoutSubviews NS_REQUIRES_SUPER; +- (void)viewDidLayoutSubviews NS_REQUIRES_SUPER; + +@end