Peter 3b155750f5 Add 'submodules/lottie-ios/' from commit 'd40e390fbe6d7ef3b417876af6fdce5e4d2aa335'
git-subtree-dir: submodules/lottie-ios
git-subtree-mainline: 76e5a7fab6b0222780f346530cdbeeff96f3e105
git-subtree-split: d40e390fbe6d7ef3b417876af6fdce5e4d2aa335
2019-06-11 18:54:21 +01:00

317 lines
9.2 KiB
Objective-C

//
// LAAnimatedTextField.m
// LottieExamples
//
// Created by Brandon Withrow on 1/10/17.
// Copyright © 2017 Brandon Withrow. All rights reserved.
//
#import "AnimatedTextField.h"
#import <Lottie/Lottie.h>
@interface LACharacterCell : UICollectionViewCell
- (void)setCharacter:(NSString *)character;
- (void)displayCharacter:(BOOL)animated;
- (void)loopAnimation;
@end
@implementation LACharacterCell {
LOTAnimationView *animationView_;
NSString *character_;
}
- (void)prepareForReuse {
[super prepareForReuse];
}
- (void)setCharacter:(NSString *)character {
NSString *sanitizedCharacter = [character substringToIndex:1];
NSCharacterSet *alphaSet = [NSCharacterSet letterCharacterSet];
BOOL valid = [[sanitizedCharacter stringByTrimmingCharactersInSet:alphaSet] isEqualToString:@""];
if ([character isEqualToString:@"BlinkingCursor"]) {
sanitizedCharacter = character;
}
if ([sanitizedCharacter isEqualToString:@","]) {
sanitizedCharacter = @"Comma";
valid = YES;
}
if ([sanitizedCharacter isEqualToString:@"'"]) {
sanitizedCharacter = @"Apostrophe";
valid = YES;
}
if ([sanitizedCharacter isEqualToString:@":"]) {
sanitizedCharacter = @"Colon";
valid = YES;
}
if ([sanitizedCharacter isEqualToString:@"?"]) {
sanitizedCharacter = @"QuestionMark";
valid = YES;
}
if ([sanitizedCharacter isEqualToString:@"!"]) {
sanitizedCharacter = @"ExclamationMark";
valid = YES;
}
if ([sanitizedCharacter isEqualToString:@"."]) {
sanitizedCharacter = @"Period";
valid = YES;
}
if ([sanitizedCharacter isEqualToString:character_]) {
return;
}
[animationView_ removeFromSuperview];
animationView_ = nil;
character_ = nil;
if (!valid) {
return;
}
character_ = sanitizedCharacter;
LOTAnimationView *animationView = [LOTAnimationView animationNamed:sanitizedCharacter];
animationView_ = animationView;
animationView_.contentMode = UIViewContentModeScaleAspectFit;
[self.contentView addSubview:animationView_];
CGRect c = self.contentView.bounds;
animationView_.frame = CGRectMake(-c.size.width, 0, c.size.width * 3, c.size.height);
}
- (void)layoutSubviews {
[super layoutSubviews];
CGRect c = self.contentView.bounds;
animationView_.frame = CGRectMake(-c.size.width, 0, c.size.width * 3, c.size.height);
}
- (void)displayCharacter:(BOOL)animated {
if (animated) {
[animationView_ play];
} else {
animationView_.animationProgress = 1;
}
}
- (void)loopAnimation {
animationView_.loopAnimation = YES;
}
@end
@interface AnimatedTextField () <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
@end
@implementation AnimatedTextField {
NSString *_text;
UICollectionView *_collectionView;
UICollectionViewFlowLayout *_layout;
BOOL _updatingCells;
NSArray *letterSizes_;
}
#pragma mark -- UIView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_layout = [[UICollectionViewFlowLayout alloc] init];
_fontSize = 36;
_collectionView = [[UICollectionView alloc] initWithFrame:self.bounds collectionViewLayout:_layout];
[_collectionView registerClass:[LACharacterCell class] forCellWithReuseIdentifier:@"char"];
_collectionView.delegate = self;
_collectionView.dataSource = self;
_collectionView.backgroundColor = [UIColor whiteColor];
[self addSubview:_collectionView];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
_collectionView.frame = self.bounds;
}
#pragma mark -- External
- (void)setFontSize:(NSInteger)fontSize {
_fontSize = fontSize;
[self _computeLetterSizes];
[_layout invalidateLayout];
}
- (void)scrollToBottom {
CGPoint bottomOffset = CGPointMake(0, _collectionView.contentSize.height - _collectionView.bounds.size.height + _collectionView.contentInset.bottom);
bottomOffset.y = MAX(bottomOffset.y, 0);
[_collectionView setContentOffset:bottomOffset animated:YES];
}
- (void)setScrollInsets:(UIEdgeInsets)scrollInsets {
[_collectionView setContentInset:scrollInsets];
[self scrollToBottom];
}
- (void)setText:(NSString *)text {
_text = text;
[self _computeLetterSizes];
[_collectionView reloadData];
[self scrollToBottom];
}
- (void)changeCharactersInRange:(NSRange)range
toString:(NSString *)replacementString {
NSMutableString *newText = [_text mutableCopy];
if (range.location > 0) {
[newText replaceCharactersInRange:range withString:replacementString];
}
NSMutableArray *updateIndices, *addIndices, *removeIndices;
for (NSUInteger i = range.location; i < newText.length; i ++) {
if (i < _text.length) {
if (!updateIndices) {
updateIndices = [NSMutableArray array];
}
[updateIndices addObject:[NSIndexPath indexPathForRow:i inSection:0]];
} else {
if (!addIndices) {
addIndices = [NSMutableArray array];
}
[addIndices addObject:[NSIndexPath indexPathForRow:i inSection:0]];
}
}
for (NSUInteger i = newText.length; i < _text.length; i ++) {
if (!removeIndices) {
removeIndices = [NSMutableArray array];
}
[removeIndices addObject:[NSIndexPath indexPathForRow:i inSection:0]];
}
_updatingCells = YES;
_text = newText;
[self _computeLetterSizes];
[_collectionView performBatchUpdates:^{
if (addIndices) {
[_collectionView insertItemsAtIndexPaths:addIndices];
}
if (updateIndices) {
[_collectionView reloadItemsAtIndexPaths:updateIndices];
}
if (removeIndices) {
[_collectionView deleteItemsAtIndexPaths:removeIndices];
}
} completion:^(BOOL finished) {
_updatingCells = NO;
}];
[self scrollToBottom];
}
#pragma mark -- Internal
- (NSString*)_characterAtIndexPath:(NSIndexPath *)indexPath {
return [_text substringWithRange:NSMakeRange(indexPath.row, 1)].uppercaseString;
}
- (void)_computeLetterSizes {
NSMutableArray *sizes = [NSMutableArray array];
CGFloat width = self.bounds.size.width;
CGFloat currentWidth = 0;
for (NSInteger i = 0; i < _text.length; i ++) {
NSString *letter = [_text substringWithRange:NSMakeRange(i, 1)].uppercaseString;
CGSize letterSize = [self _sizeOfString:letter];
if ([letter isEqualToString:@" "] && i + 1 < _text.length) {
NSString *cutString = [_text substringFromIndex:i + 1];
NSArray *words = [cutString componentsSeparatedByString:@" "];
if (words.count) {
CGSize nextWordLength = [self _sizeOfString:words.firstObject];
if (currentWidth + nextWordLength.width + letterSize.width > width) {
letterSize.width = floorf(width - currentWidth);
currentWidth = 0;
} else {
currentWidth += letterSize.width;
}
}
} else {
currentWidth += letterSize.width;
if (currentWidth >= width) {
currentWidth = letterSize.width;
}
}
[sizes addObject:[NSValue valueWithCGSize:letterSize]];
}
CGSize cursorSize = [self _sizeOfString:@"W"];
[sizes addObject:[NSValue valueWithCGSize:cursorSize]];
letterSizes_ = sizes;
}
- (CGSize)_sizeOfString:(NSString *)string {
CGSize constraint = CGSizeMake(1000, 1000);
CGSize textSize = [string.uppercaseString boundingRectWithSize:constraint
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{ NSFontAttributeName : [UIFont boldSystemFontOfSize:self.fontSize] }
context:nil].size;
textSize.width += (string.length * 2);
return textSize;
}
#pragma mark -- UICollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section {
return _text.length + 1;
}
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath {
LACharacterCell *charCell = [collectionView dequeueReusableCellWithReuseIdentifier:@"char" forIndexPath:indexPath];
return charCell;
}
- (void)collectionView:(UICollectionView *)collectionView
willDisplayCell:(LACharacterCell *)cell
forItemAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row < _text.length) {
[cell setCharacter:[self _characterAtIndexPath:indexPath]];
[cell displayCharacter:_updatingCells];
} else {
[cell setCharacter:@"BlinkingCursor"];
[cell loopAnimation];
[cell displayCharacter:YES];
}
}
#pragma mark -- UICollectionViewDelegate
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout*)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row >= letterSizes_.count) {
return CGSizeZero;
}
NSValue *value = letterSizes_[indexPath.row];
return value.CGSizeValue;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
return 0;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
minimumLineSpacingForSectionAtIndex:(NSInteger)section {
return 0;
}
@end