Skip to content
This repository has been archived by the owner on Oct 30, 2018. It is now read-only.

Adds custom typing indicator support #207

Merged
merged 11 commits into from
Jul 2, 2015
4 changes: 2 additions & 2 deletions Examples/Messenger-Shared/MessageTableViewCell.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@

#import <UIKit/UIKit.h>

#define kAvatarSize 30.0
#define kMinimumHeight 50.0
static CGFloat kMessageTableViewCellMinimumHeight = 50.0;
static CGFloat kMessageTableViewCellAvatarHeight = 30.0;

@interface MessageTableViewCell : UITableViewCell

Expand Down
21 changes: 11 additions & 10 deletions Examples/Messenger-Shared/MessageTableViewCell.m
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,19 @@ - (void)configureSubviews
@"attachmentView": self.attachmentView,
};

NSDictionary *metrics = @{@"tumbSize": @(kAvatarSize),
@"trailing": @10,
@"leading": @5,
NSDictionary *metrics = @{@"tumbSize": @(kMessageTableViewCellAvatarHeight),
@"padding": @15,
@"right": @10,
@"left": @5,
@"attchSize": @80,
};

[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-leading-[thumbnailView(tumbSize)]-trailing-[titleLabel(>=0)]-trailing-|" options:0 metrics:metrics views:views]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-leading-[thumbnailView(tumbSize)]-trailing-[bodyLabel(>=0)]-trailing-|" options:0 metrics:metrics views:views]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-leading-[thumbnailView(tumbSize)]-trailing-[attachmentView]-trailing-|" options:0 metrics:metrics views:views]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-left-[thumbnailView(tumbSize)]-right-[titleLabel(>=0)]-right-|" options:0 metrics:metrics views:views]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-left-[thumbnailView(tumbSize)]-right-[bodyLabel(>=0)]-right-|" options:0 metrics:metrics views:views]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-left-[thumbnailView(tumbSize)]-right-[attachmentView]-right-|" options:0 metrics:metrics views:views]];

[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-trailing-[thumbnailView(tumbSize)]-(>=0)-|" options:0 metrics:metrics views:views]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-15-[titleLabel]-leading-[bodyLabel(>=0)]-leading-[attachmentView(>=0,<=attchSize)]-trailing-|" options:0 metrics:metrics views:views]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-right-[thumbnailView(tumbSize)]-(>=0)-|" options:0 metrics:metrics views:views]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-padding-[titleLabel]-left-[bodyLabel(>=0)]-left-[attachmentView(>=0,<=attchSize)]-right-|" options:0 metrics:metrics views:views]];
}

- (void)prepareForReuse
Expand Down Expand Up @@ -100,7 +101,7 @@ - (UIImageView *)thumbnailView
_thumbnailView.userInteractionEnabled = NO;
_thumbnailView.backgroundColor = [UIColor colorWithWhite:0.9 alpha:1.0];

_thumbnailView.layer.cornerRadius = kAvatarSize/2.0;
_thumbnailView.layer.cornerRadius = kMessageTableViewCellAvatarHeight/2.0;
_thumbnailView.layer.masksToBounds = YES;
}
return _thumbnailView;
Expand All @@ -115,7 +116,7 @@ - (UIImageView *)attachmentView
_attachmentView.backgroundColor = [UIColor clearColor];
_attachmentView.contentMode = UIViewContentModeCenter;

_attachmentView.layer.cornerRadius = kAvatarSize/4.0;
_attachmentView.layer.cornerRadius = kMessageTableViewCellAvatarHeight/4.0;
_attachmentView.layer.masksToBounds = YES;
}
return _attachmentView;
Expand Down
63 changes: 48 additions & 15 deletions Examples/Messenger-Shared/MessageViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@
#import "MessageViewController.h"
#import "MessageTableViewCell.h"
#import "MessageTextView.h"
#import "TypingIndicatorView.h"
#import "Message.h"

#import <LoremIpsum/LoremIpsum.h>

#define DEBUG_CUSTOM_TYPING_INDICATOR 0

static NSString *MessengerCellIdentifier = @"MessengerCell";
static NSString *AutoCompletionCellIdentifier = @"AutoCompletionCell";

Expand All @@ -34,8 +37,7 @@ - (id)init
{
self = [super initWithTableViewStyle:UITableViewStylePlain];
if (self) {
// Register a subclass of SLKTextView, if you need any special appearance and/or behavior customisation.
[self registerClassForTextView:[MessageTextView class]];
[self commonInit];
}
return self;
}
Expand All @@ -44,8 +46,7 @@ - (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
// Register a subclass of SLKTextView, if you need any special appearance and/or behavior customisation.
[self registerClassForTextView:[MessageTextView class]];
[self commonInit];
}
return self;
}
Expand All @@ -55,6 +56,17 @@ + (UITableViewStyle)tableViewStyleForCoder:(NSCoder *)decoder
return UITableViewStylePlain;
}

- (void)commonInit
{
// Register a SLKTextView subclass, if you need any special appearance and/or behavior customisation.
[self registerClassForTextView:[MessageTextView class]];

#if DEBUG_CUSTOM_TYPING_INDICATOR
// Register a UIView subclass, conforming to SLKTypingIndicatorProtocol, to use a custom typing indicator view.
[self registerClassForTypingIndicatorView:[TypingIndicatorView class]];
#endif
}


#pragma mark - View lifecycle

Expand Down Expand Up @@ -111,7 +123,9 @@ - (void)viewDidLoad
self.textInputbar.counterStyle = SLKCounterStyleSplit;
self.textInputbar.counterPosition = SLKCounterPositionTop;

#if !DEBUG_CUSTOM_TYPING_INDICATOR
self.typingIndicatorView.canResignByTouch = YES;
#endif

[self.autoCompletionView registerClass:[MessageTableViewCell class] forCellReuseIdentifier:AutoCompletionCellIdentifier];
[self registerPrefixesForAutoCompletion:@[@"@", @"#", @":"]];
Expand Down Expand Up @@ -145,8 +159,23 @@ - (void)fillWithText:(id)sender

- (void)simulateUserTyping:(id)sender
{
if (!self.isEditing && !self.isAutoCompleting) {
if ([self canShowTypingIndicator]) {

#if DEBUG_CUSTOM_TYPING_INDICATOR
__block TypingIndicatorView *view = (TypingIndicatorView *)self.typingIndicatorProxyView;

CGFloat scale = [UIScreen mainScreen].scale;
CGSize imgSize = CGSizeMake(kTypingIndicatorViewAvatarHeight*scale, kTypingIndicatorViewAvatarHeight*scale);

// This will cause the typing indicator to show after a delay ¯\_(ツ)_/¯
[LoremIpsum asyncPlaceholderImageWithSize:imgSize
completion:^(UIImage *image) {
UIImage *thumbnail = [UIImage imageWithCGImage:image.CGImage scale:scale orientation:UIImageOrientationUp];
[view presentIndicatorWithName:[LoremIpsum name] image:thumbnail];
}];
#else
[self.typingIndicatorView insertUsername:[LoremIpsum name]];
#endif
}
}

Expand Down Expand Up @@ -322,6 +351,15 @@ - (BOOL)canPressRightButton
return [super canPressRightButton];
}

- (BOOL)canShowTypingIndicator
{
#if DEBUG_CUSTOM_TYPING_INDICATOR
return YES;
#else
return [super canShowTypingIndicator];
#endif
}

- (BOOL)canShowAutoCompletion
{
NSArray *array = nil;
Expand Down Expand Up @@ -414,12 +452,7 @@ - (MessageTableViewCell *)messageCellForRowAtIndexPath:(NSIndexPath *)indexPath
if (cell.needsPlaceholder)
{
CGFloat scale = [UIScreen mainScreen].scale;

if ([[UIScreen mainScreen] respondsToSelector:@selector(nativeScale)]) {
scale = [UIScreen mainScreen].nativeScale;
}

CGSize imgSize = CGSizeMake(kAvatarSize*scale, kAvatarSize*scale);
CGSize imgSize = CGSizeMake(kMessageTableViewCellAvatarHeight*scale, kMessageTableViewCellAvatarHeight*scale);

[LoremIpsum asyncPlaceholderImageWithSize:imgSize
completion:^(UIImage *image) {
Expand Down Expand Up @@ -470,7 +503,7 @@ - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPa
NSDictionary *attributes = @{NSFontAttributeName: [UIFont systemFontOfSize:16.0],
NSParagraphStyleAttributeName: paragraphStyle};

CGFloat width = CGRectGetWidth(tableView.frame)-kAvatarSize;
CGFloat width = CGRectGetWidth(tableView.frame)-kMessageTableViewCellAvatarHeight;
width -= 25.0;

CGRect titleBounds = [message.username boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:NULL];
Expand All @@ -487,14 +520,14 @@ - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPa
height += 80.0 + 10.0;
}

if (height < kMinimumHeight) {
height = kMinimumHeight;
if (height < kMessageTableViewCellMinimumHeight) {
height = kMessageTableViewCellMinimumHeight;
}

return height;
}
else {
return kMinimumHeight;
return kMessageTableViewCellMinimumHeight;
}
}

Expand Down
20 changes: 20 additions & 0 deletions Examples/Messenger-Shared/TypingIndicatorView.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// TypingIndicatorView.h
// Messenger
//
// Created by Ignacio Romero Z. on 6/27/15.
// Copyright (c) 2015 Slack Technologies, Inc. All rights reserved.
//

#import <UIKit/UIKit.h>
#import "SLKTypingIndicatorProtocol.h"

static CGFloat kTypingIndicatorViewMinimumHeight = 80.0;
static CGFloat kTypingIndicatorViewAvatarHeight = 30.0;

@interface TypingIndicatorView : UIView <SLKTypingIndicatorProtocol>

- (void)presentIndicatorWithName:(NSString *)name image:(UIImage *)image;
- (void)dismissIndicator;

@end
175 changes: 175 additions & 0 deletions Examples/Messenger-Shared/TypingIndicatorView.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
//
// TypingIndicatorView.m
// Messenger
//
// Created by Ignacio Romero Z. on 6/27/15.
// Copyright (c) 2015 Slack Technologies, Inc. All rights reserved.
//

#import "TypingIndicatorView.h"

@interface TypingIndicatorView ()
@property (nonatomic, strong) UIImageView *thumbnailView;
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) CAGradientLayer *backgroundGradient;
@end

@implementation TypingIndicatorView
@synthesize visible = _visible;

- (instancetype)init
{
self = [super init];
if (self) {
[self configureSubviews];
}
return self;
}

- (void)configureSubviews
{
[self addSubview:self.thumbnailView];
[self addSubview:self.titleLabel];
[self.layer insertSublayer:self.backgroundGradient atIndex:0];

NSDictionary *views = @{@"thumbnailView": self.thumbnailView,
@"titleLabel": self.titleLabel
};

NSDictionary *metrics = @{@"invertedThumbSize": @(-kTypingIndicatorViewAvatarHeight/2.0),
};

[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-5-[thumbnailView]-10-[titleLabel]-(>=0)-|" options:0 metrics:metrics views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(>=0)-[thumbnailView]-(invertedThumbSize)-|" options:0 metrics:metrics views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(>=0)-[titleLabel]-(3@750)-|" options:0 metrics:metrics views:views]];
}


#pragma mark - SLKTypingIndicatorProtocol

- (CGSize)intrinsicContentSize
{
return CGSizeMake(UIViewNoIntrinsicMetric, [self height]);
}

- (void)dismissIndicator
{
if (self.isVisible) {
self.visible = NO;
}
}


#pragma mark - UIView

- (void)layoutSubviews
{
[super layoutSubviews];

self.backgroundGradient.frame = self.bounds;
}


#pragma mark - Getters

- (UIImageView *)thumbnailView
{
if (!_thumbnailView) {
_thumbnailView = [UIImageView new];
_thumbnailView.translatesAutoresizingMaskIntoConstraints = NO;
_thumbnailView.userInteractionEnabled = NO;
_thumbnailView.backgroundColor = [UIColor grayColor];
_thumbnailView.contentMode = UIViewContentModeTopLeft;

_thumbnailView.layer.cornerRadius = kTypingIndicatorViewAvatarHeight/2.0;
_thumbnailView.layer.masksToBounds = YES;
}
return _thumbnailView;
}

- (UILabel *)titleLabel
{
if (!_titleLabel) {
_titleLabel = [UILabel new];
_titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
_titleLabel.backgroundColor = [UIColor clearColor];
_titleLabel.userInteractionEnabled = NO;
_titleLabel.numberOfLines = 1;
_titleLabel.contentMode = UIViewContentModeTopLeft;

_titleLabel.font = [UIFont systemFontOfSize:12.0];
_titleLabel.textColor = [UIColor lightGrayColor];
}
return _titleLabel;
}

- (CAGradientLayer *)backgroundGradient
{
if (!_backgroundGradient) {
_backgroundGradient = [CAGradientLayer layer];
_backgroundGradient.frame = CGRectMake(0.0, 0.0, [UIScreen mainScreen].bounds.size.width, [self height]);

_backgroundGradient.colors = @[(id)[UIColor colorWithWhite:1.0 alpha:0].CGColor,
(id)[UIColor colorWithWhite:1.0 alpha:0.9].CGColor,
(id)[UIColor colorWithWhite:1.0 alpha:1.0].CGColor];

_backgroundGradient.locations = @[@0, @0.5, @1];
_backgroundGradient.rasterizationScale = [UIScreen mainScreen].scale;
_backgroundGradient.shouldRasterize = YES;
}
return _backgroundGradient;
}

- (CGFloat)height
{
CGFloat height = 13.0;
height += self.titleLabel.font.lineHeight;
return height;
}


#pragma mark - TypingIndicatorView

- (void)presentIndicatorWithName:(NSString *)name image:(UIImage *)image
{
if (self.isVisible || name.length == 0 || !image) {
return;
}

NSString *text = [NSString stringWithFormat:@"%@ is typing...", name];

NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:text];
[attributedString addAttributes:@{NSFontAttributeName: [UIFont boldSystemFontOfSize:12.0]} range:[text rangeOfString:name]];

self.titleLabel.attributedText = attributedString;
self.thumbnailView.image = image;

self.visible = YES;
}


#pragma mark - Hit Testing

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];

[self dismissIndicator];
}


#pragma mark - Lifeterm

- (void)dealloc
{
_titleLabel = nil;
_thumbnailView = nil;
_backgroundGradient = nil;
}

@end
Loading