1
0
mirror of https://github.com/danog/Telegram.git synced 2024-12-03 09:57:46 +01:00
Telegram/Telegraph/TGTokenFieldView.m
2015-10-01 19:19:52 +03:00

599 lines
16 KiB
Objective-C

#import "TGTokenFieldView.h"
#import "TGTokenView.h"
#import "TGBackspaceTextField.h"
#import "TGHacks.h"
#import "TGImageUtils.h"
#import <QuartzCore/QuartzCore.h>
@interface TGTokenFieldScrollView : UIScrollView
@end
@implementation TGTokenFieldScrollView
- (BOOL)touchesShouldCancelInContentView:(UIView *)__unused view
{
return true;
}
- (void)scrollRectToVisible:(CGRect)__unused rect animated:(BOOL)__unused animated
{
}
@end
@interface TGTokenFieldView () <UITextFieldDelegate, UIKeyInput>
{
bool _wasEmpty;
}
@property (nonatomic, strong) NSMutableDictionary *tokenAnimations;
@property (nonatomic, strong) NSMutableArray *tokenList;
@property (nonatomic, strong) TGBackspaceTextField *textField;
@property (nonatomic) float lineHeight;
@property (nonatomic) float linePadding;
@property (nonatomic) float lineSpacing;
@property (nonatomic) int maxNumberOfLines;
@property (nonatomic) int currentNumberOfLines;
@property (nonatomic, strong) UIView *shadowView;
@end
@implementation TGTokenFieldView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self != nil)
{
[self commonInit];
}
return self;
}
- (void)commonInit
{
_lineHeight = 26;
_linePadding = 9;
_lineSpacing = 11;
_maxNumberOfLines = 2;
_currentNumberOfLines = 1;
_shadowView = [[UIView alloc] init];
CGFloat separatorHeight = TGIsRetina() ? 0.5f : 1.0f;
_shadowView.frame = CGRectMake(0, self.frame.size.height, self.frame.size.width, separatorHeight);
_shadowView.backgroundColor = TGSeparatorColor();
_shadowView.layer.zPosition = 1;
[self addSubview:_shadowView];
self.clipsToBounds = false;
self.backgroundColor = UIColorRGB(0xf8f8f8);
_scrollView = [[TGTokenFieldScrollView alloc] initWithFrame:self.bounds];
_scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_scrollView.delaysContentTouches = true;
_scrollView.canCancelContentTouches = true;
_scrollView.scrollsToTop = false;
_scrollView.backgroundColor = UIColorRGB(0xf8f8f8);
_scrollView.opaque = true;
[self addSubview:_scrollView];
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapRecognized:)];
tapRecognizer.cancelsTouchesInView = false;
[_scrollView addGestureRecognizer:tapRecognizer];
_textField = [[TGBackspaceTextField alloc] initWithFrame:CGRectMake(0, 0, 10, 42)];
_textField.text = @"";
_textField.delegate = self;
_textField.autocorrectionType = UITextAutocorrectionTypeNo;
_textField.autocapitalizationType = UITextAutocapitalizationTypeNone;
_textField.backgroundColor = UIColorRGB(0xf8f8f8);
_textField.font = [UIFont systemFontOfSize:15];
_textField.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
[_textField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];
[_scrollView addSubview:_textField];
_textField.customPlaceholderLabel.text = _placeholder;
[_textField.customPlaceholderLabel sizeToFit];
_textField.customPlaceholderLabel.frame = CGRectOffset(_textField.customPlaceholderLabel.frame, 9 + 5, 9 + 4);
[_scrollView addSubview:_textField.customPlaceholderLabel];
_tokenList = [[NSMutableArray alloc] init];
}
- (void)setPlaceholder:(NSString *)placeholder
{
_placeholder = placeholder;
_textField.customPlaceholderLabel.text = _placeholder;
[_textField.customPlaceholderLabel sizeToFit];
}
- (void)addToken:(NSString *)title tokenId:(id)tokenId animated:(bool)animated
{
TGTokenView *tokenView = [[TGTokenView alloc] initWithFrame:CGRectMake(0, 0, 20, 28)];
tokenView.label = title;
tokenView.tokenId = tokenId;
[_tokenList addObject:tokenView];
[_scrollView addSubview:tokenView];
if (animated)
{
if (_tokenAnimations == nil)
_tokenAnimations = [[NSMutableDictionary alloc] init];
if (tokenId != nil)
[_tokenAnimations setObject:[[NSNumber alloc] initWithInt:1] forKey:tokenId];
}
[_textField setShowPlaceholder:false animated:_textField.text.length == 0];
[self updateCounter];
[self setNeedsLayout];
}
- (void)updateCounter
{
}
- (NSArray *)tokenIds
{
NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:_tokenList.count];
for (TGTokenView *tokenView in _tokenList)
{
if (tokenView.tokenId != nil)
[array addObject:tokenView.tokenId];
}
return array;
}
- (void)removeTokensAtIndexes:(NSIndexSet *)indexSet
{
NSUInteger lastCount = _tokenList.count;
[indexSet enumerateIndexesUsingBlock:^(NSUInteger index, __unused BOOL *stop)
{
TGTokenView *tokenView = [_tokenList objectAtIndex:index];
if ([tokenView isFirstResponder])
[tokenView resignFirstResponder];
[UIView animateWithDuration:0.2 animations:^
{
tokenView.transform = CGAffineTransformMakeScale(0.1f, 0.1f);
tokenView.alpha = 0.0f;
} completion:^(__unused BOOL finished)
{
[tokenView removeFromSuperview];
}];
}];
[_tokenList removeObjectsAtIndexes:indexSet];
if (_tokenAnimations == nil)
_tokenAnimations = [[NSMutableDictionary alloc] init];
[self setNeedsLayout];
if (_tokenList.count == 0)
[_textField setShowPlaceholder:true animated:lastCount != _tokenList.count];
[self updateCounter];
}
- (float)preferredHeight
{
int visibleNumberOfLines = MIN(MAX(1, _currentNumberOfLines), _maxNumberOfLines);
return _lineHeight * visibleNumberOfLines + MAX(0, visibleNumberOfLines - 1) * _lineSpacing + _linePadding * 2;
}
- (void)setFrame:(CGRect)frame
{
[super setFrame:frame];
CGFloat separatorHeight = TGIsRetina() ? 0.5f : 1.0f;
_shadowView.frame = CGRectMake(0.0f, frame.size.height, frame.size.width, separatorHeight);
}
- (void)layoutSubviews
{
[super layoutSubviews];
[self doLayout:_tokenAnimations != nil];
if (_tokenAnimations != nil)
{
for (TGTokenView *tokenView in _tokenList)
{
if (tokenView.tokenId == nil)
continue;
NSNumber *nAnimation = [_tokenAnimations objectForKey:tokenView.tokenId];
if (nAnimation != nil)
{
tokenView.transform = CGAffineTransformMakeScale(0.1f, 0.1f);
tokenView.alpha = 0.0f;
}
}
[UIView animateWithDuration:0.2 animations:^
{
for (TGTokenView *tokenView in _tokenList)
{
if (tokenView.tokenId == nil)
continue;
NSNumber *nAnimation = [_tokenAnimations objectForKey:tokenView.tokenId];
if (nAnimation != nil)
{
tokenView.transform = CGAffineTransformIdentity;
tokenView.alpha = 1.0f;
}
}
}];
_tokenAnimations = nil;
}
}
- (void)doLayout:(bool)animated
{
float width = (float)self.frame.size.width;
const float textFieldMinWidth = 60;
const float padding = 9;
const float textFieldPadding = 5;
const float spacing = 1;
int currentLine = 0;
float currentX = padding;
float currentY = _linePadding;
float additionalPadding = 0;
CGRect targetFrames[_tokenList.count];
memset(targetFrames, 0, sizeof(CGRect) * _tokenList.count);
int index = -1;
for (TGTokenView *tokenView in _tokenList)
{
index++;
CGFloat tokenWidth = [tokenView preferredWidth];
if (width - padding - currentX - additionalPadding < MAX(tokenWidth, textFieldMinWidth) && currentX > padding + FLT_EPSILON)
{
currentLine++;
currentY += _lineHeight + _lineSpacing;
currentX = padding;
}
CGRect tokenFrame = CGRectMake(currentX, currentY - 1, MIN(tokenWidth, width - padding - currentX - additionalPadding), tokenView.frame.size.height);
if (animated && tokenView.frame.origin.x > FLT_EPSILON)
targetFrames[index] = tokenFrame;
else
tokenView.frame = tokenFrame;
currentX += tokenFrame.size.width + spacing;
}
bool lastLineContainsTextFieldOnly = false;
if (width - padding - currentX - additionalPadding < textFieldMinWidth)
{
currentLine++;
currentY += _lineHeight + _lineSpacing;
currentX = padding;
lastLineContainsTextFieldOnly = true;
}
if (currentLine + 1 != _currentNumberOfLines)
animated = true;
CGRect textFieldFrame = CGRectMake(currentX + textFieldPadding, currentY + 4 - 12, width - padding - currentX - textFieldPadding * 2 - additionalPadding + 4, _textField.frame.size.height);
_textField.frame = textFieldFrame;
if (animated)
{
_textField.alpha = 0.0f;
[UIView animateWithDuration:0.2 animations:^
{
_textField.alpha = 1.0f;
}];
}
if (lastLineContainsTextFieldOnly && ![self hasFirstResponder])
{
currentLine--;
currentY -= _lineHeight + _lineSpacing;
}
if (animated)
{
[UIView beginAnimations:@"tokenField" context:nil];
[UIView setAnimationDuration:0.15];
int index = -1;
for (TGTokenView *tokenView in _tokenList)
{
index++;
if (targetFrames[index].origin.x > FLT_EPSILON)
tokenView.frame = targetFrames[index];
}
}
currentY += _lineHeight + _linePadding;
_scrollView.contentSize = CGSizeMake(_scrollView.frame.size.width, currentY);
if (animated)
{
[UIView commitAnimations];
}
if (MIN(currentLine + 1, _maxNumberOfLines) != MIN(_currentNumberOfLines, _maxNumberOfLines))
{
id <TGTokenFieldViewDelegate> delegate = _delegate;
[delegate tokenFieldView:self didChangeHeight:_lineHeight * MIN(currentLine + 1, _maxNumberOfLines) + MAX(0, currentLine) * _lineSpacing + _linePadding * 2];
}
else if (currentLine + 1 > _currentNumberOfLines)
{
[self scrollToTextField:true];
}
_currentNumberOfLines = currentLine + 1;
}
- (bool)hasFirstResponder
{
return _textField.isFirstResponder;
//return [self findFirstResponder:_scrollView] != nil;
}
- (UIView *)findFirstResponder:(UIView *)view
{
if ([view isFirstResponder])
return view;
for (UIView *subview in view.subviews)
{
UIView *result = [self findFirstResponder:subview];
if (result != nil)
return result;
}
return nil;
}
#pragma mark -
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
if (textField == _textField)
{
//[self addToken:_textField.text];
bool wasEmpty = textField.text.length == 0;
textField.text = @"";
if (_delegate != nil)
{
id <TGTokenFieldViewDelegate> delegate = _delegate;
[delegate tokenFieldView:self didChangeText:textField.text];
if (wasEmpty != textField.text.length == 0)
[delegate tokenFieldView:self didChangeSearchStatus:[self searchIsActive] byClearingTextField:true];
}
[self scrollToTextField:false];
_textField.hidden = true;
dispatch_async(dispatch_get_main_queue(), ^
{
_textField.hidden = false;
});
}
return false;
}
- (void)textFieldDidChange:(UITextField *)textField
{
_textField.customPlaceholderLabel.hidden = (textField.text.length != 0);
[self scrollToTextField:true];
id <TGTokenFieldViewDelegate> delegate = _delegate;
if (delegate != nil)
{
[delegate tokenFieldView:self didChangeText:textField.text];
if (_wasEmpty != (textField.text.length == 0))
[delegate tokenFieldView:self didChangeSearchStatus:[self searchIsActive] byClearingTextField:true];
}
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)__unused range replacementString:(NSString *)__unused string
{
if (textField == _textField)
_wasEmpty = textField.text.length == 0;
return true;
}
- (void)textFieldDidHitLastBackspace
{
if (_tokenList.count != 0)
{
[[_tokenList lastObject] becomeFirstResponder];
}
}
- (void)textFieldDidBecomeFirstResponder
{
[self setNeedsLayout];
}
- (void)textFieldDidResignFirstResponder
{
if (_tokenAnimations == nil)
_tokenAnimations = [[NSMutableDictionary alloc] init];
[self setNeedsLayout];
}
- (void)scrollToTextField:(bool)animated
{
CGPoint contentOffset = _scrollView.contentOffset;
CGSize contentSize = _scrollView.contentSize;
CGSize frameSize = _scrollView.frame.size;
if (contentOffset.y < contentSize.height - frameSize.height)
contentOffset = CGPointMake(0, contentSize.height - frameSize.height);
if (contentOffset.y < 0)
contentOffset.y = 0;
if (!animated)
[_scrollView setContentOffset:contentOffset animated:animated];
else
{
[UIView animateWithDuration:0.2 animations:^
{
[_scrollView setContentOffset:contentOffset animated:false];
}];
}
}
- (bool)searchIsActive
{
return /*_textField.isFirstResponder && */_textField.text.length != 0;
}
- (void)clearText
{
_textField.text = @"";
id <TGTokenFieldViewDelegate> delegate = _delegate;
if (delegate != nil)
[delegate tokenFieldView:self didChangeSearchStatus:[self searchIsActive] byClearingTextField:false];
}
- (void)highlightToken:(TGTokenView *)tokenView
{
for (TGTokenView *view in _tokenList)
{
if (view != tokenView)
{
if (view.selected)
view.selected = false;
}
}
tokenView.selected = true;
[self setNeedsLayout];
}
- (void)unhighlightToken:(TGTokenView *)tokenView
{
tokenView.selected = false;
if (_tokenAnimations == nil)
_tokenAnimations = [[NSMutableDictionary alloc] init];
[self setNeedsLayout];
}
- (void)deleteToken:(TGTokenView *)tokenView
{
int index = -1;
for (TGTokenView *view in _tokenList)
{
index++;
if (view == tokenView)
{
[_tokenList removeObjectAtIndex:index];
break;
}
}
[tokenView removeFromSuperview];
[_textField becomeFirstResponder];
[self setNeedsLayout];
id <TGTokenFieldViewDelegate> delegate = _delegate;
if (delegate != nil)
[delegate tokenFieldView:self didDeleteTokenWithId:tokenView.tokenId];
if (_tokenList.count == 0)
[_textField setShowPlaceholder:true animated:false];
[self updateCounter];
}
- (void)beginTransition:(NSTimeInterval)duration
{
UIImage *inputFieldImage = nil;
UIImageView *temporaryImageView = nil;
UIGraphicsBeginImageContextWithOptions(_scrollView.bounds.size, true, 0.0f);
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
inputFieldImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
temporaryImageView = [[UIImageView alloc] initWithImage:inputFieldImage];
temporaryImageView.frame = _scrollView.bounds;
UIView *temporaryImageViewContainer = [[UIView alloc] initWithFrame:_scrollView.frame];
temporaryImageViewContainer.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
temporaryImageViewContainer.clipsToBounds = true;
[temporaryImageViewContainer addSubview:temporaryImageView];
[self insertSubview:temporaryImageViewContainer aboveSubview:_scrollView];
_scrollView.alpha = 0.0f;
[UIView animateWithDuration:duration animations:^
{
temporaryImageView.alpha = 0.0f;
_scrollView.alpha = 1.0f;
} completion:^(__unused BOOL finished)
{
[temporaryImageView removeFromSuperview];
[temporaryImageViewContainer removeFromSuperview];
}];
}
- (void)tapRecognized:(UITapGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateRecognized)
{
[_textField becomeFirstResponder];
}
}
- (BOOL)hasText
{
return false;
}
- (void)insertText:(NSString *)__unused text
{
}
- (void)deleteBackward
{
}
@end