• This repository has been archived on 30/Oct/2018
  • Stars
    star
    8,332
  • Rank 4,189 (Top 0.09 %)
  • Language
    Objective-C
  • License
    MIT License
  • Created over 9 years ago
  • Updated over 5 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

⛔️**DEPRECATED** ⛔️ A drop-in UIViewController subclass with a growing text input view and other useful messaging features

Deprecation

We are no longer providing support for SlackTextViewController. This project satisfied all of our iOS messaging needs in the past and we are proud to have contributed it to the open-source community. Today, in order to delight our users with a solution that is highly tailored and rapidly iterated-upon, we have shifted focus to our internal projects. Unfortunately, this renders us lacking the capacity to support our past projects in addition to our newer, internal projects. This project has been deprecated as a result.

SlackTextViewController

IMPORTANT NOTICE: Please update to >= 1.9 to avoid any risk of app rejection. More details in #361

License Pod Version Carthage compatible BuddyBuild

A drop-in UIViewController subclass with a growing text input view and other useful messaging features. Meant to be a replacement for UITableViewController & UICollectionViewController.

Demo Gif

This library was historically used in our iOS app. At its inception, the library satisfied our product needs and was flexible enough to be reused by others wanting to build great messaging apps for iOS.

Feature List

Core

Additional

Compatibility

  • Carthage & CocoaPods
  • Objective-C & Swift
  • iOS 7, 8 & 9
  • iPhone & iPad
  • Storyboard
  • UIPopOverController & UITabBarController
  • Container View Controller
  • Auto-Rotation
  • iPad Multitasking (iOS 9 only)
  • Localization

Installation

With CocoaPods:
pod "SlackTextViewController"
With Carthage:
github "slackhq/SlackTextViewController"
Manually:

There are two ways to do this:

  • Copy and drag the Source/ folder to your project.
  • or compile the project located in Builder/SlackTextViewController.xcodeproj to create a SlackTextViewController.framework package. You could also link the library into your project.

How to use

Subclassing

SLKTextViewController is meant to be subclassed, like you would normally do with UITableViewController or UICollectionViewController or UIScrollView. This pattern is a convenient way of extending UIViewController. SlackTextViewController manages a lot behind the scenes while still providing the ability to add custom behaviours. You may override methods, and decide to call super and perform additional logic, or not to call super and override default logic.

Start by creating a new subclass of SLKTextViewController.

In the init overriding method, if you wish to use the UITableView version, call:

Obj-C
[super initWithTableViewStyle:UITableViewStylePlain]
Swift
super.init(tableViewStyle: .Plain)

or the UICollectionView version:

Obj-C
[super initWithCollectionViewLayout:[UICollectionViewFlowLayout new]]
Swift
super.init(collectionViewLayout: UICollectionViewFlowLayout())

or the UIScrollView version:

Obj-C
[super initWithScrollView:self.myStrongScrollView]
Swift
super.init(scrollView: self.myStrongScrollView)

Protocols like UITableViewDelegate and UITableViewDataSource are already setup for you. You will be able to call whatever delegate and data source methods you need for customising your control.

Calling [super init] will call [super initWithTableViewStyle:UITableViewStylePlain] by default.

Storyboard

When using SlackTextViewController with storyboards, instead of overriding the traditional initWithCoder: you will need to override any of the two custom methods below. This approach helps preserving the exact same features from the programatic approach, but also limits the edition of the nib of your SLKTextViewController subclass since it doesn't layout subviews from the nib (subviews are still initialized and layed out programatically).

if you wish to use the UITableView version, call:

Obj-C
+ (UITableViewStyle)tableViewStyleForCoder:(NSCoder *)decoder
{
    return UITableViewStylePlain;
}
Swift
override class func tableViewStyleForCoder(decoder: NSCoder) -> UITableViewStyle {
    return .Plain
}

or the UICollectionView version:

Obj-C
+ (UICollectionViewLayout *)collectionViewLayoutForCoder:(NSCoder *)decoder
{
    return [UICollectionViewFlowLayout new];
}
Swift
override class func collectionViewLayoutForCoder(decoder: NSCoder) -> UICollectionViewLayout {
    return UICollectionViewFlowLayout()
}

Sample Project

Check out the sample project, everything is demo'd there. There are 2 main examples (different targets) for testing the programatic and storyboard approaches, and a Swift example. Most of the features are implemented for you to quickly start using them.

Feel free to contribute!

Features

Growing Text View

Growing

The text view expands automatically when a new line is required, until it reaches its maxNumberOfLinesvalue. You may change this property's value in the textView.

By default, the number of lines is set to best fit each device dimensions:

  • iPhone 4 (<=480pts): 4 lines
  • iPhone 5/6 (>=568pts): 6 lines
  • iPad (>=768pts): 8 lines

On iPhone devices, in landscape orientation, the maximum number of lines is changed to fit the available space.

Inverted Mode

Some layouts may require to show from bottom to top and new subviews are inserted from the bottom. To enable this, you must use the inverted flag property (default is YES/true). This will actually invert the entire ScrollView object. Make sure to apply the same transformation to every subview. In the case of UITableView, the best place for adjusting the transformation is in its data source methods like:

Obj-C
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:kCellIdentifier];
    cell.transform = self.tableView.transform;
}
Swift
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        
    if let cell = tableView.dequeueReusableCellWithIdentifier(kCellIdentifier) {
        cell.transform = self.tableView.transform
    }
}

Autocompletion

We use autocompletion for many things: names, channels, emoji, and more.

Autocompletion

To set up autocompletion in your app, follow these simple steps:

1. Registration

You must first register all the prefixes you'd like to support for autocompletion detection:

Obj-C
[self registerPrefixesForAutoCompletion:@[@"#"]];
Swift
self.registerPrefixesForAutoCompletion(["@", "#"])

2. Processing

Every time a new character is inserted in the text view, the nearest word to the caret will be processed and verified if it contains any of the registered prefixes.

Once the prefix has been detected, didChangeAutoCompletionPrefix:andWord: will be called. This is the perfect place to populate your data source and show/hide the autocompletion view. So you must override it in your subclass, to be able to perform additional tasks. Default returns NO.

Obj-C
- (void)didChangeAutoCompletionPrefix:(NSString *)prefix andWord:(NSString *)word
{
    NSArray *array = [NSArray arrayWithArray:self.channels];
    
    if ([prefix isEqualToString:@"#"] && word.length > 0) {
        self.searchResult = [array filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"self BEGINSWITH[c]", word]];
    }
    
    BOOL show = (self.searchResult.count > 0);
    
    [self showAutoCompletionView:show];
}
Swift
override func didChangeAutoCompletionPrefix(prefix: String, andWord word: String) {
    
    let array: NSArray = self.channels
    
    if prefix == "#" && word.characters.count > 0 {
        self.searchResult = array.filteredArrayUsingPredicate(NSPredicate(format: "self BEGINSWITH[c] %@", word))
    }
    
    let show = (self.searchResult.count > 0)
    
    self.showAutoCompletionView(show)
}

The autocompletion view is a UITableView instance, so you will need to use UITableViewDataSource to populate its cells. You have complete freedom for customizing the cells.

You don't need to call reloadData yourself, since it will be invoked automatically right after calling the showAutoCompletionView method.

3. Layout

The maximum height of the autocompletion view is set to 140 pts by default. You can update this value anytime, so the view automatically adjusts based on the amount of displayed cells.

Obj-C
- (CGFloat)heightForAutoCompletionView
{
    CGFloat cellHeight = 34.0;
    return cellHeight*self.searchResult.count;
}
Swift
override func heightForAutoCompletionView() -> CGFloat {
    let cellHeight:CGFloat = 34
    return cellHeight * CGFloat(self.searchResult.count)
}

4. Confirmation

If the user selects any autocompletion view cell on tableView:didSelectRowAtIndexPath:, you must call acceptAutoCompletionWithString: to commit autocompletion. That method expects a string matching the selected item, that you would like to be inserted in the text view.

Obj-C
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if ([tableView isEqual:self.autoCompletionView]) {
        
        NSMutableString *item = [self.searchResult[indexPath.row] mutableCopy];
        [item appendString:@" "]; // Adding a space helps dismissing the auto-completion view
        
        [self acceptAutoCompletionWithString:item keepPrefix:YES];
    }
}
Swift
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        
    if tableView.isEqual(self.autoCompletionView) {
        var item = self.searchResult[indexPath.row]
        item += " "  // Adding a space helps dismissing the auto-completion view
            
        self.acceptAutoCompletionWithString(item)
    }
}

The autocompletion view will automatically be dismissed and the chosen string will be inserted in the text view, replacing the detected prefix and word.

You can always call cancelAutoCompletion to exit the autocompletion mode and refresh the UI.

Edit Mode

Edit Mode

To enable edit mode, you simply need to call editText:, and the text input will switch to edit mode, removing both left and right buttons, extending the input bar a bit higher with "Accept" and "Cancel" buttons. Both of this buttons are accessible in the SLKTextInputbar instance for customisation.

To capture the "Accept" or "Cancel" events, you must override the following methods.

Obj-C
- (void)didCommitTextEditing:(id)sender
{
    NSString *message = [self.textView.text copy];
    
    [self.messages removeObjectAtIndex:0];
    [self.messages insertObject:message atIndex:0];
    [self.tableView reloadData];
    
    [super didCommitTextEditing:sender];
}

- (void)didCancelTextEditing:(id)sender
{
    [super didCancelTextEditing:sender];
}
Swift
override func didCommitTextEditing(sender: AnyObject) {
    
    let message:String = self.textView.text
    
    self.messages.removeAtIndex(0)
    self.messages.insert(message, atIndex: 0)
    
    self.tableView!.reloadData()
    
    super.didCommitTextEditing(sender)
}
 
override func didCancelTextEditing(sender: AnyObject) {
        
    super.didCancelTextEditing(sender)
}

Notice that you must call super at some point, so the text input exits the edit mode, re-adjusting the layout and clearing the text view. Use the editing property to know if the editing mode is on.

Markdown Formatting

Markdown Formatting

You can register markdown formatting symbols so they can easily be used to wrap a text selection, with the help of the native contextual menu, aka UIMenuController. This feature doesn't take care of the rendering of the markdown: it's sole purpose is to ease the formatting tools to the user. Optionally, you can enable autoCompleteFormatting so any pending markdown closure symbol can be added automatically after double tapping on the keyboard spacebar, just like the native gesture to add a sentence period. The sentence period is still being added as a fallback.

Markdown Formatting Animated

1. Registration

You must first register the formatting symbol and assign a title string to be used in the menu controller item.

Obj-C
[self.textView registerMarkdownFormattingSymbol:@"*" withTitle:@"Bold"];
Swift
self.textView.registerMarkdownFormattingSymbol("*", withTitle: "Bold")

2. Customisation

Futher more, you can customise some of the behavior for special formatting cases, using the UITextViewDelegate methods. In the following example, we don't present the Quote formatting in the contextual menu when the text selection isn't a paragraph.

Obj-C
- (BOOL)textView:(SLKTextView *)textView shouldOfferFormattingForSymbol:(NSString *)symbol
{
    if ([symbol isEqualToString:@">"]) {
        
        NSRange selection = textView.selectedRange;
        
        // The Quote formatting only applies new paragraphs
        if (selection.location == 0 && selection.length > 0) {
            return YES;
        }
        
        // or older paragraphs too
        NSString *prevString = [textView.text substringWithRange:NSMakeRange(selection.location-1, 1)];
        
        if ([[NSCharacterSet newlineCharacterSet] characterIsMember:[prevString characterAtIndex:0]]) {
            return YES;
        }

        return NO;
    }
    
    return [super textView:textView shouldOfferFormattingForSymbol:symbol];
}

In this other method implementation, we don't want to allow auto-completion for the Quote formatting since it doesn't require a closure.

Obj-C
- (BOOL)textView:(SLKTextView *)textView shouldInsertSuffixForFormattingWithSymbol:(NSString *)symbol prefixRange:(NSRange)prefixRange
{
    if ([symbol isEqualToString:@">"]) {
        return NO;
    }
    
    return [super textView:textView shouldInsertSuffixForFormattingWithSymbol:symbol prefixRange:prefixRange];
}

Typing Indicator

Typing Indicator

Optionally, you can enable a simple typing indicator, which will be displayed right above the text input. It shows the name of the people that are typing, and if more than 2, it will display "Several are typing" message.

To enable the typing indicator, just call:

Obj-C
[self.typingIndicatorView insertUsername:@"John"];
Swift
self.typingIndicatorView?.insertUsername("John")

and the view will automatically be animated on top of the text input. After a default interval of 6 seconds, if the same name hasn't been assigned once more, the view will be dismissed with animation.

You can remove names from the list by calling:

Obj-C
[self.typingIndicatorView removeUsername:@"John"];
Swift
self.typingIndicatorView?.removeUsername("John")

You can also dismiss it by calling:

Obj-C
[self.typingIndicatorView dismissIndicator];
Swift
self.typingIndicatorView?.dismissIndicator()

Panning Gesture

Dismissing the keyboard with a panning gesture is enabled by default with the keyboardPanningEnabled property. You can always disable it if you'd like. You can extend the verticalPanGesture behaviors with the UIGestureRecognizerDelegate methods.

Hideable TextInputbar

Sometimes you may need to hide the text input bar. Very similar to UINavigationViewController's API, simply do:

Obj-C
[self setTextInputbarHidden:YES animated:YES];
Swift
self.setTextInputbarHidden(true, animated: true)

Shake Gesture

Shake Gesture

A shake gesture to clear text is enabled by default with the undoShakingEnabled property.

You can optionally override willRequestUndo, to implement your UI to ask the users if he would like to clean the text view's text. If there is not text entered, the method will not be called.

If you don't override willRequestUndo and undoShakingEnabled is set to YES/true, a system alert will be shown.

External Keyboard

There a few basic key commands enabled by default:

  • cmd + z -> undo
  • shift + cmd + z -> redo
  • return key -> calls didPressRightButton:, or didCommitTextEditing: if in edit mode
  • shift/cmd + return key -> line break
  • escape key -> exits edit mode, or auto-completion mode, or dismisses the keyboard
  • up & down arrows -> vertical cursor movement

To add additional key commands, simply override keyCommands and append super's array.

Obj-C
- (NSArray *)keyCommands
{
    NSMutableArray *commands = [NSMutableArray arrayWithArray:[super keyCommands]];
    
    // Edit last message
    [commands addObject:[UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow
                                           modifierFlags:0
                                                   action:@selector(editLastMessage:)]];
    
    return commands;
}
Swift
override var keyCommands: [UIKeyCommand]? {
        
    var commands = super.keyCommands
        
    // Edit last message
    let command = UIKeyCommand(input: UIKeyInputUpArrow, modifierFlags: .Command, action: "editLastMessage:")
    commands?.append(command)
        
    return commands
}

There are also a set of useful flags for keyboard special detections such as isExternalKeyboardDetected, isKeyboardUndocked, typingSuggestionEnabled and isTrackpadEnabled (iOS 9 only)

Dynamic Type

Dynamic Type is enabled by default with the dynamicTypeEnabled property. You can always disable it if you'd like, but the text input bar would still adjust to best fit the font size of the text view.

Dynamic-Type

Xcode Templates

Template

We have prepared a set of useful Xcode templates so you can quickly start using SlackTextViewController.

To install them, open up your terminal and type:

sh ./SlackTextViewController/File\ Templates/install.sh

These templates are also available in Alcatraz.

More Repositories

1

nebula

A scalable overlay networking tool with a focus on performance, simplicity and security
Go
13,646
star
2

PanModal

An elegant and highly customizable presentation API for constructing bottom sheet modals on iOS.
Swift
3,595
star
3

go-audit

go-audit is an alternative to the auditd daemon that ships with many distros
Go
1,541
star
4

circuit

⚡️ A Compose-driven architecture for Kotlin and Android applications.
Kotlin
1,250
star
5

EitherNet

A pluggable sealed API result type for modeling Retrofit responses.
Kotlin
730
star
6

goSDL

goSDL
PHP
516
star
7

slack-api-docs

API Docs for Slack.com
427
star
8

slack-gradle-plugin

Gradle and IntelliJ build tooling used in Slack's Android repo
Kotlin
418
star
9

compose-lints

Lint checks to aid with a healthy adoption of Compose
Kotlin
349
star
10

keeper

A Gradle plugin that infers Proguard/R8 keep rules for androidTest sources.
Kotlin
248
star
11

slack-lints

A collection of custom Android/Kotlin lint checks we use in our Android and Kotlin code bases at Slack.
Kotlin
207
star
12

magic-cli

Ruby
196
star
13

astra

Astra is a cloud-native search and analytics engine for log, trace, and audit data
Java
189
star
14

simple-kubernetes-webhook

This project is aimed at illustrating how to build a fully functioning kubernetes admission webhook in the simplest way possible.
Go
170
star
15

csp-html-webpack-plugin

A plugin which, when combined with HTMLWebpackPlugin, adds CSP tags to the HTML output.
JavaScript
158
star
16

hack-sql-fake

A library for testing database driven code in Hack
Hack
75
star
17

hakana

Another typechecker for Hack, built by Slack
Rust
70
star
18

vscode-hack

Hack language & HHVM debugger support for Visual Studio Code
TypeScript
70
star
19

gsuite-oauth-third-party-app-report

Start enforcing G Suite third-party apps via OAuth
JavaScript
54
star
20

backend-interview-prep-questions

A few questions & data to help you prepare for the Slack HQ backend interview
PLpgSQL
45
star
21

moshi-gson-interop

An interop tool for safely mixing Moshi and Gson models in JSON serialization.
Kotlin
43
star
22

kotlin-cli-util

Kotlin CLI utilities, mostly intended for use with Clikt
Kotlin
33
star
23

tree-sitter-hack

Hack grammar for tree-sitter
JavaScript
28
star
24

hack-json-schema

Generate Hack JSON Schema validators based on a JSON Schema.
Hack
27
star
25

deanimator

Go package that can detect animated images and "deanimate" them by rendering just the first frame as a static image.
Go
24
star
26

es-query-simple

A tiny command line utility to query elasticsearch. "
Python
23
star
27

auto-value-kotlin

An AutoValue extension that generates binary and source compatible equivalent Kotlin data classes of AutoValue models.
Kotlin
23
star
28

go-rsyslog-pstats

Parses and forwards rsyslog process stats to a local statsite, statsd, or wire protocol compatible service.
Go
21
star
29

tiny-thumb

Novel, efficient, and practical image compression with visually appealing results. 🤏 ✨
Go
14
star
30

backend-interview-prerequisites

A project to ensure that your backend onsite interview at Slack runs smoothly.
Go
11
star
31

sqlite-go-connect

A simple go app that connects to a sqlite3 database
Go
11
star
32

sqlite-python-connect

Short bit of code to connect to a sqlite db and run a query in python
Python
10
star
33

hack-graphql

Playground for a hack graphql server
Hack
8
star
34

protoc-gen-ts

A Typescript Protocol Buffer Implementation from the Future ✨
TypeScript
8
star
35

htmlsanitizer-hack

A port of the PHP HTML Purifier originally developed by Edward Z. Yang into Hacklang
Hack
7
star
36

sqlite-java-connect

This is a minimal repo project that connects to a sqlite3 database and returns a single row.
Java
6
star
37

grpc-hack

A gRPC extension for HHVM
C++
4
star
38

slack-astra-app

Grafana plugin that adds support for Astra
TypeScript
4
star
39

sqlite-ruby-connect

Just a tiny lil something to connect to SQLite using Ruby
PLpgSQL
3
star
40

proto-hack

hacklang generator for protobuf
Hack
3
star
41

snow

Python
2
star
42

.github

1
star
43

go-metrics-prometheus

Go
1
star
44

quota

1
star