From 0b87c305643f6723b2c490a0beb42d31d33654c1 Mon Sep 17 00:00:00 2001 From: steven yang Date: Tue, 26 Nov 2013 19:23:37 +0800 Subject: [PATCH 1/6] Fixed For Xcode4.6,customize abbreviation and token height MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Fixed: Property ' edgesForExtendedLayout' not found 'edgesForExtendedLayout' is only available in iOS 7.0 or later. If you compile 'TokenFieldExample' in iOS 6.1 SDK, you should get this error. You can check for it at compile-time using something like: #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 if ([self respondsToSelector:@selector(edgesForExtendedLayout)]) self.edgesForExtendedLayout = UIRectEdgeNone; #endif 2. Add 'abbr' property I used property 'abbr' and category ' NSString+truncateToSize' if users like customize their untoken text like 'Victoria Delgadillo... 4 联系人' rather than default '3 recipients'. 3. can designate height By default, the token field's height is fixed to 43 px. In iPad,it is fine.In iPhone,it is too tall. Maybe we should shorten it to 24 px so we can save more screen dimensions. Specify the frame parameter in ' initWithFrame ' no help for it.I refined the code so users can customize the token field's height. --- TITokenField.h | 3 + TITokenField.m | 58 ++++-- .../Classes/NSString+truncateToSize.h | 29 +++ .../Classes/NSString+truncateToSize.m | 181 ++++++++++++++++++ .../Classes/TokenFieldExampleViewController.m | 5 +- .../project.pbxproj | 6 + 6 files changed, 269 insertions(+), 13 deletions(-) create mode 100644 TokenFieldExample/Classes/NSString+truncateToSize.h create mode 100644 TokenFieldExample/Classes/NSString+truncateToSize.m diff --git a/TITokenField.h b/TITokenField.h index bb0cd1e..70d97a5 100644 --- a/TITokenField.h +++ b/TITokenField.h @@ -61,6 +61,7 @@ @property (nonatomic, assign) BOOL shouldSortResults; @property (nonatomic, assign) BOOL shouldSearchInBackground; @property (nonatomic, readonly) TITokenField * tokenField; +@property (nonatomic, assign)float tokenFieldHeight; @property (nonatomic, readonly) UIView * separator; @property (nonatomic, readonly) UITableView * resultsTable; @property (nonatomic, readonly) UIView * contentView; @@ -68,6 +69,7 @@ @property (weak, nonatomic, readonly) NSArray * tokenTitles; - (void)updateContentSize; +-(id)initWithFrame:(CGRect)frame withFieldHeight:(float)_height; @end @@ -90,6 +92,7 @@ typedef enum { @property (nonatomic, assign) BOOL removesTokensOnEndEditing; @property (nonatomic, readonly) int numberOfLines; @property (nonatomic, strong) NSCharacterSet * tokenizingCharacters; +@property (nonatomic, strong) NSString* abbr; - (void)addToken:(TIToken *)title; - (TIToken *)addTokenWithTitle:(NSString *)title; diff --git a/TITokenField.m b/TITokenField.m index edd8d0d..6872076 100644 --- a/TITokenField.m +++ b/TITokenField.m @@ -8,6 +8,7 @@ #import "TITokenField.h" #import +#import "NSString+truncateToSize.h" @interface TITokenField () @property (nonatomic, assign) BOOL forcePickSearchResult; @@ -30,6 +31,7 @@ @implementation TITokenFieldView { UIView * _contentView; NSMutableArray * _resultsArray; UIPopoverController * _popoverController; + float _tokenFieldHeight; } @dynamic delegate; @synthesize showAlreadyTokenized = _showAlreadyTokenized; @@ -44,7 +46,7 @@ @implementation TITokenFieldView { @synthesize sourceArray = _sourceArray; #pragma mark Init -- (instancetype)initWithFrame:(CGRect)frame { +- (instancetype)initWithFrame:(CGRect)frame{ if ((self = [super initWithFrame:frame])){ [self setup]; @@ -52,7 +54,13 @@ - (instancetype)initWithFrame:(CGRect)frame { return self; } - +-(id)initWithFrame:(CGRect)frame withFieldHeight:(float)_height{ + if ((self=[super initWithFrame:frame])) { + _tokenFieldHeight=_height; + [self setup]; + } + return self; +} - (instancetype)initWithCoder:(NSCoder *)aDecoder { if ((self = [super initWithCoder:aDecoder])){ @@ -75,7 +83,7 @@ - (void)setup { _shouldSearchInBackground = NO; _resultsArray = [NSMutableArray array]; - _tokenField = [[TITokenField alloc] initWithFrame:CGRectMake(0, 0, self.bounds.size.width, 42)]; + _tokenField = [[TITokenField alloc] initWithFrame:CGRectMake(0, 0, self.bounds.size.width, _tokenFieldHeight?_tokenFieldHeight:42)]; [_tokenField addTarget:self action:@selector(tokenFieldDidBeginEditing:) forControlEvents:UIControlEventEditingDidBegin]; [_tokenField addTarget:self action:@selector(tokenFieldDidEndEditing:) forControlEvents:UIControlEventEditingDidEnd]; [_tokenField addTarget:self action:@selector(tokenFieldTextDidChange:) forControlEvents:UIControlEventEditingChanged]; @@ -431,11 +439,13 @@ @implementation TITokenField { @synthesize selectedToken = _selectedToken; @synthesize tokenizingCharacters = _tokenizingCharacters; @synthesize forcePickSearchResult = _forcePickSearchResult; - +@synthesize abbr=_abbr; +static float _height; #pragma mark Init - (instancetype)initWithFrame:(CGRect)frame { - + _height=frame.size.height; if ((self = [super initWithFrame:frame])){ + _height=frame.size.height; [self setup]; } @@ -531,7 +541,12 @@ - (NSArray *)tokenObjects { - (UIScrollView *)scrollView { return ([self.superview isKindOfClass:[UIScrollView class]] ? (UIScrollView *)self.superview : nil); } - +-(NSString*)abbr{ + if (_abbr) { + return _abbr; + }else + return @"联系人"; +} #pragma mark Event Handling - (BOOL)becomeFirstResponder { return (_editable ? [super becomeFirstResponder] : NO); @@ -562,7 +577,22 @@ - (void)didEndEditing { CGFloat availableWidth = self.bounds.size.width - self.leftView.bounds.size.width - self.rightView.bounds.size.width; if (_tokens.count > 1 && untokSize.width > availableWidth){ - untokenized = [NSString stringWithFormat:@"%d recipients", titles.count]; +// untokenized = [NSString stringWithFormat:@"%d recipients", titles.count]; + NSString* endString= [NSString stringWithFormat:@"... %d %@", titles.count,self.abbr]; + CGSize endSize=[endString sizeWithFont:[UIFont systemFontOfSize:14]]; + + availableWidth-=endSize.width; + + NSString* truncatedString=[untokenized truncateToSize:CGSizeMake(availableWidth, 0) + withFont:[UIFont systemFontOfSize:14] + lineBreakMode:UILineBreakModeTailTruncation]; + truncatedString=[untokenized truncateWordsToFit:CGSizeMake(availableWidth, 20) + withInset:0.0 + usingFont:[UIFont systemFontOfSize:14] + wordSplitter:@","]; + + untokenized=[NSString stringWithFormat:@"%@%@",truncatedString,endString]; + } } @@ -725,7 +755,8 @@ - (void)tokenTouchUpInside:(TIToken *)token { - (CGFloat)layoutTokensInternal { - CGFloat topMargin = floor(self.font.lineHeight * 4 / 7); +// CGFloat topMargin = floor(self.font.lineHeight * 4 / 7); + CGFloat topMargin = floor(_height-self.font.lineHeight)/2; CGFloat leftMargin = self.leftViewWidth + 12; CGFloat hPadding = 8; CGFloat rightMargin = self.rightViewWidth + hPadding; @@ -873,12 +904,17 @@ - (CGRect)placeholderRectForBounds:(CGRect)bounds { } - (CGRect)leftViewRectForBounds:(CGRect)bounds { - return ((CGRect){{8, ceilf(self.font.lineHeight * 4 / 7)}, self.leftView.bounds.size}); +// return ((CGRect){{8, ceilf(self.font.lineHeight * 4 / 7)}, self.leftView.bounds.size}); + return ((CGRect){{10,floor((_height-self.font.lineHeight)/2)},self.leftView.bounds.size}); } - (CGRect)rightViewRectForBounds:(CGRect)bounds { - return ((CGRect){{bounds.size.width - self.rightView.bounds.size.width - 6, - bounds.size.height - self.rightView.bounds.size.height - 6}, self.rightView.bounds.size}); +// return ((CGRect){{bounds.size.width - self.rightView.bounds.size.width - 6, +// bounds.size.height - self.rightView.bounds.size.height - 6}, self.rightView.bounds.size}); + float bottomLineY=bounds.size.height-_height; + return ((CGRect){{bounds.size.width - self.rightView.bounds.size.width - 6, + bottomLineY+(_height - self.rightView.bounds.size.height )/2}, self.rightView.bounds.size}); + } - (CGFloat)leftViewWidth { diff --git a/TokenFieldExample/Classes/NSString+truncateToSize.h b/TokenFieldExample/Classes/NSString+truncateToSize.h new file mode 100644 index 0000000..fccf40c --- /dev/null +++ b/TokenFieldExample/Classes/NSString+truncateToSize.h @@ -0,0 +1,29 @@ +// +// NSString+FitRect.h +// MyContentView +// +// Created by chen neng on 13-8-16. +// +// + +#import + +// +// NSString-truncateToSize +// Fast Fonts +// +// Created by Stuart Shelton on 28/03/2010. +// Copyright 2010 Stuart Shelton. All rights reserved. +// + +@interface NSString (truncateToSize) + +- (NSString *)truncateToSize: (CGSize)size withFont: (UIFont *)font lineBreakMode: (UILineBreakMode)lineBreakMode; +- (NSString *)truncateToSize: (CGSize)size withFont: (UIFont *)font lineBreakMode: (UILineBreakMode)lineBreakMode withAnchor: (NSString *)anchor; +- (NSString *)truncateToSize: (CGSize)size withFont: (UIFont *)font lineBreakMode: (UILineBreakMode)lineBreakMode withStartingAnchor: (NSString *)startingAnchor withEndingAnchor: (NSString *)endingAnchor; +- (NSString *)truncateWordsToFit:(CGSize)fitSize + withInset:(CGFloat)inset + usingFont:(UIFont *)font + wordSplitter:(NSString*)splitter; +@end + diff --git a/TokenFieldExample/Classes/NSString+truncateToSize.m b/TokenFieldExample/Classes/NSString+truncateToSize.m new file mode 100644 index 0000000..2281c32 --- /dev/null +++ b/TokenFieldExample/Classes/NSString+truncateToSize.m @@ -0,0 +1,181 @@ +// +// NSString+FitRect.m +// MyContentView +// +// Created by chen neng on 13-8-16. +// +// + +#import "NSString+truncateToSize.h" + +@implementation NSString (truncateToSize) + /* (NSString *)truncateToSize: withFont: lineBreakMode: */ +- (NSString *)truncateToSize: (CGSize)size withFont: (UIFont *)font lineBreakMode: (UILineBreakMode)lineBreakMode { + return [self truncateToSize: size withFont: font lineBreakMode: lineBreakMode withStartingAnchor: nil withEndingAnchor: nil]; +} + /* (NSString *)truncateToSize: withFont: lineBreakMode: withAnchor: */ +- (NSString *)truncateToSize: (CGSize)size withFont: (UIFont *)font lineBreakMode: (UILineBreakMode)lineBreakMode withAnchor: (NSString *)anchor { + return [self truncateToSize: size withFont: font lineBreakMode: lineBreakMode withStartingAnchor: anchor withEndingAnchor: anchor]; +} +/* (NSString *)truncateToSize: withFont: lineBreakMode: withStartingAnchor: withEndingAnchor: */ +- (NSString *)truncateToSize: (CGSize)size withFont: (UIFont *)font lineBreakMode: (UILineBreakMode)lineBreakMode withStartingAnchor: (NSString *)startingAnchor withEndingAnchor: (NSString *)endingAnchor { + if( !( lineBreakMode & ( UILineBreakModeHeadTruncation | UILineBreakModeMiddleTruncation | UILineBreakModeTailTruncation ) ) ) + return self; + if( [self sizeWithFont: font].width <= size.width ) + return self; + + NSString *ellipsis = @"…"; + + // Note that this code will find the first occurrence of any given anchor, + // so be careful when choosing anchor characters/strings... + NSInteger start; + if( startingAnchor ) { + // Exact character-by-character equivalence,case-sensitively + start = [self rangeOfString: startingAnchor options: NSLiteralSearch].location; + if( NSNotFound == start ) {// If not found + if( [startingAnchor isEqualToString: endingAnchor] ) + return [self truncateToSize: size withFont: font lineBreakMode: lineBreakMode]; + else + return [self truncateToSize: size withFont: font lineBreakMode: lineBreakMode withAnchor: endingAnchor]; + } + } else { + start = 0; + } + + NSUInteger end; + if( endingAnchor ) { + end = [self rangeOfString: endingAnchor options: NSLiteralSearch range: NSMakeRange( start + 1, [self length] - start - 1 )].location; + if( NSNotFound == end ) {// If not found + if( [startingAnchor isEqualToString: endingAnchor] ) + // Shouldn't ever occur, since filtered out in block above... + return [self truncateToSize: size withFont: font lineBreakMode: lineBreakMode]; + else + return [self truncateToSize: size withFont: font lineBreakMode: lineBreakMode withAnchor: startingAnchor]; + } + } else { + end = [self length]; + } + + NSUInteger targetLength = end - start; + if( [[self substringWithRange: NSMakeRange( start, targetLength )] sizeWithFont: font].width < [ellipsis sizeWithFont: font].width ) + if( startingAnchor || endingAnchor ){ + return [self truncateToSize: size withFont: font lineBreakMode: lineBreakMode]; + } + + NSMutableString *truncatedString = [[NSMutableString alloc] initWithString: self]; + /* To void an "enumeration not handled in switch" warning arising,add a default clause statement or use : + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wswitch" + ...... + #pragma clang diagnostic pop + */ + switch(lineBreakMode){ + case UILineBreakModeHeadTruncation: + // Avoid anchor... + if( startingAnchor ) + start++; + while( targetLength > [ellipsis length] + 1 && [truncatedString sizeWithFont: font].width > size.width) { + // Replace our ellipsis and one additional following character with our ellipsis + NSRange range = NSMakeRange( start, [ellipsis length] + 1 ); + [truncatedString replaceCharactersInRange: range withString: ellipsis]; + targetLength--; + } + break; + + case UILineBreakModeMiddleTruncation: + { + NSUInteger leftEnd = start + ( targetLength / 2 ); + NSUInteger rightStart = leftEnd + 1; + + if( leftEnd + 1 <= rightStart - 1 ) + break; + + // leftPre and rightPost consist of any characters before and beyond + // any specified anchor(s). + // left and right are the two halves of the string to be truncated - although + // the initial split is still performed based upon the length of the + // (sub)string to be truncated, so we could still make a bad initial split given + // a short string with predominantly narrow characters on one side and wide + // characters on the other. + NSString *leftPre = @""; + if( startingAnchor ) + leftPre = [truncatedString substringWithRange: NSMakeRange( 0, start + 1 )]; + NSMutableString *left = [NSMutableString stringWithString: [truncatedString substringWithRange: NSMakeRange( ( startingAnchor ? start + 1 : start ), leftEnd - start )]]; + NSMutableString *right = [NSMutableString stringWithString: [truncatedString substringWithRange: NSMakeRange( rightStart, end - rightStart )]]; + NSString *rightPost = @""; + if( endingAnchor ) + rightPost = [truncatedString substringWithRange: NSMakeRange( end, [truncatedString length] - end )]; + + /* DLog( @"pre '%@', left '%@', right '%@', post '%@'", leftPre, left, right, rightPost ); */ + // Reassemble substrings + [truncatedString setString: [NSString stringWithFormat: @"%@%@%@%@%@", leftPre, left, ellipsis, right, rightPost]]; + + while( leftEnd > start + 1 && rightStart < end + 1 && [truncatedString sizeWithFont: font].width > size.width) { + CGFloat leftLength = [left sizeWithFont: font].width; + CGFloat rightLength = [right sizeWithFont: font].width; + + // Shorten string of longest width + if( leftLength > rightLength ) { + [left deleteCharactersInRange: NSMakeRange( [left length] - 1, 1 )]; + leftEnd--; + } else { /* ( leftLength < = rightLength ) */ + [right deleteCharactersInRange: NSMakeRange( 0, 1 )]; + rightStart++; + } + + /* DLog( @"pre '%@', left '%@', right'%@', post '%@'", leftPre, left, right, rightPost ); */ + [truncatedString setString: [NSString stringWithFormat: @"%@%@%@%@%@", leftPre, left, ellipsis, right, rightPost]]; + } + } + break; + + case UILineBreakModeTailTruncation: + while( targetLength > [ellipsis length] + 1 && [truncatedString sizeWithFont: font].width > size.width) { + // Remove last character + NSRange range = NSMakeRange( --end, 1); + [truncatedString deleteCharactersInRange: range]; + // Replace original last-but-one (now last) character with our ellipsis... + range = NSMakeRange( end - [ellipsis length], [ellipsis length] ); + [truncatedString replaceCharactersInRange: range withString: ellipsis]; + targetLength--; + } + break; + default: + break; + } + + NSString *result = [NSString stringWithString: truncatedString]; + return result; +} +- (NSString *)truncateWordsToFit:(CGSize)fitSize + withInset:(CGFloat)inset + usingFont:(UIFont *)font + wordSplitter:(NSString*)splitter +{ + NSString *result = [self copy]; + CGSize maxSize = CGSizeMake(fitSize.width - (inset * 2), FLT_MAX); + CGSize size = [result sizeWithFont:font + constrainedToSize:maxSize + lineBreakMode:UILineBreakModeWordWrap]; + NSRange range; + + if (fitSize.height < size.height) + while (fitSize.height < size.height) { + + range = [result rangeOfString:splitter + options:NSBackwardsSearch]; + + if (range.location != NSNotFound && range.location > 0 ) { + result = [result substringToIndex:range.location]; + } else { + result = [result substringToIndex:result.length - 1]; + } + + size = [result sizeWithFont:font + constrainedToSize:maxSize + lineBreakMode:UILineBreakModeWordWrap]; + } + + return result; +} +@end \ No newline at end of file diff --git a/TokenFieldExample/Classes/TokenFieldExampleViewController.m b/TokenFieldExample/Classes/TokenFieldExampleViewController.m index bf634f5..6cd3dd3 100644 --- a/TokenFieldExample/Classes/TokenFieldExampleViewController.m +++ b/TokenFieldExample/Classes/TokenFieldExampleViewController.m @@ -21,14 +21,15 @@ @implementation TokenFieldExampleViewController { } - (void)viewDidLoad { - +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 if ([self respondsToSelector:@selector(edgesForExtendedLayout)]) self.edgesForExtendedLayout = UIRectEdgeNone; +#endif [self.view setBackgroundColor:[UIColor whiteColor]]; [self.navigationItem setTitle:@"Example"]; - _tokenFieldView = [[TITokenFieldView alloc] initWithFrame:self.view.bounds]; + _tokenFieldView = [[TITokenFieldView alloc] initWithFrame:self.view.bounds withFieldHeight:25]; [_tokenFieldView setSourceArray:[Names listOfNames]]; [self.view addSubview:_tokenFieldView]; diff --git a/TokenFieldExample/TokenFieldExample.xcodeproj/project.pbxproj b/TokenFieldExample/TokenFieldExample.xcodeproj/project.pbxproj index 6a2d853..8c4afba 100755 --- a/TokenFieldExample/TokenFieldExample.xcodeproj/project.pbxproj +++ b/TokenFieldExample/TokenFieldExample.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 1D60589B0D05DD56006BFB54 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; }; 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; }; 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; }; + 265E2A861844837B0018F47F /* NSString+truncateToSize.m in Sources */ = {isa = PBXBuildFile; fileRef = 265E2A851844837B0018F47F /* NSString+truncateToSize.m */; }; 288765A50DF7441C002DB57D /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 288765A40DF7441C002DB57D /* CoreGraphics.framework */; }; 28D7ACF80DDB3853001CB0EB /* TokenFieldExampleViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 28D7ACF70DDB3853001CB0EB /* TokenFieldExampleViewController.m */; }; E20C35C6160286F500DFF810 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E20C35C5160286F500DFF810 /* Default-568h@2x.png */; }; @@ -25,6 +26,8 @@ 1D3623250D0F684500981E51 /* TokenFieldExampleAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TokenFieldExampleAppDelegate.m; sourceTree = ""; }; 1D6058910D05DD3D006BFB54 /* TokenFieldExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TokenFieldExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 265E2A841844837B0018F47F /* NSString+truncateToSize.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+truncateToSize.h"; sourceTree = ""; }; + 265E2A851844837B0018F47F /* NSString+truncateToSize.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+truncateToSize.m"; sourceTree = ""; }; 288765A40DF7441C002DB57D /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 28D7ACF60DDB3853001CB0EB /* TokenFieldExampleViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TokenFieldExampleViewController.h; sourceTree = ""; }; 28D7ACF70DDB3853001CB0EB /* TokenFieldExampleViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TokenFieldExampleViewController.m; sourceTree = ""; }; @@ -120,6 +123,8 @@ children = ( E259CED71530FBE6002D0609 /* TITokenField.h */, E259CED81530FBE6002D0609 /* TITokenField.m */, + 265E2A841844837B0018F47F /* NSString+truncateToSize.h */, + 265E2A851844837B0018F47F /* NSString+truncateToSize.m */, E2EC195C12F4AA7A00BB0A8B /* Names.h */, E2EC195D12F4AA7A00BB0A8B /* Names.m */, ); @@ -194,6 +199,7 @@ 28D7ACF80DDB3853001CB0EB /* TokenFieldExampleViewController.m in Sources */, E2EC196012F4AA7A00BB0A8B /* Names.m in Sources */, E259CED91530FBE6002D0609 /* TITokenField.m in Sources */, + 265E2A861844837B0018F47F /* NSString+truncateToSize.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From 81311ee01656fc44202acbaa0c96e238bffcaac2 Mon Sep 17 00:00:00 2001 From: steven yang Date: Wed, 27 Nov 2013 14:16:31 +0800 Subject: [PATCH 2/6] forbid auto-searching Verbatim auto-searching always reault in UI stoping responding when source array is very huge. It is always a good thing to allow user to close the auto-searching feature although we can perform a background search. Meanwhile should allow user to execute a manual search when user need.This commit is just that. --- .gitignore | 3 ++- TITokenField.h | 3 ++- TITokenField.m | 17 ++++++++++---- .../Classes/TokenFieldExampleViewController.m | 22 ++++++++++++++++--- 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 04e924b..c5f33a2 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ */build/* *.xcworkspace -xcuserdata \ No newline at end of file +xcuserdata +*.pbxproj \ No newline at end of file diff --git a/TITokenField.h b/TITokenField.h index 70d97a5..4a4b240 100644 --- a/TITokenField.h +++ b/TITokenField.h @@ -60,6 +60,7 @@ @property (nonatomic, assign) BOOL forcePickSearchResult; @property (nonatomic, assign) BOOL shouldSortResults; @property (nonatomic, assign) BOOL shouldSearchInBackground; +@property (nonatomic, assign) BOOL stopAutoSearch; @property (nonatomic, readonly) TITokenField * tokenField; @property (nonatomic, assign)float tokenFieldHeight; @property (nonatomic, readonly) UIView * separator; @@ -70,7 +71,7 @@ - (void)updateContentSize; -(id)initWithFrame:(CGRect)frame withFieldHeight:(float)_height; - +-(void)manualSearch:(NSString*)searchString; @end //========================================================== diff --git a/TITokenField.m b/TITokenField.m index 6872076..5e231f7 100644 --- a/TITokenField.m +++ b/TITokenField.m @@ -255,10 +255,13 @@ - (void)tokenFieldDidEndEditing:(TITokenField *)field { } - (void)tokenFieldTextDidChange:(TITokenField *)field { - [self resultsForSearchString:_tokenField.text]; - - if (_forcePickSearchResult) [self setSearchResultsVisible:YES]; - else [self setSearchResultsVisible:(_resultsArray.count > 0)]; + if (!_stopAutoSearch) { + [self resultsForSearchString:_tokenField.text]; + + if (_forcePickSearchResult) [self setSearchResultsVisible:YES]; + else [self setSearchResultsVisible:(_resultsArray.count > 0)]; + + } } - (void)tokenFieldFrameWillChange:(TITokenField *)field { @@ -392,6 +395,12 @@ - (void)presentpopoverAtTokenFieldCaretAnimated:(BOOL)animated { } #pragma mark Other +-(void)manualSearch:(NSString*)searchString{ + if (_stopAutoSearch) { + [self resultsForSearchString:searchString]; + [self setSearchResultsVisible:YES]; + } +} - (NSString *)description { return [NSString stringWithFormat:@"", self, self.tokenTitles.count]; } diff --git a/TokenFieldExample/Classes/TokenFieldExampleViewController.m b/TokenFieldExample/Classes/TokenFieldExampleViewController.m index 6cd3dd3..d366412 100644 --- a/TokenFieldExample/Classes/TokenFieldExampleViewController.m +++ b/TokenFieldExample/Classes/TokenFieldExampleViewController.m @@ -32,7 +32,8 @@ - (void)viewDidLoad { _tokenFieldView = [[TITokenFieldView alloc] initWithFrame:self.view.bounds withFieldHeight:25]; [_tokenFieldView setSourceArray:[Names listOfNames]]; [self.view addSubview:_tokenFieldView]; - + [_tokenFieldView setStopAutoSearch:YES]; + [_tokenFieldView.tokenField setDelegate:self]; [_tokenFieldView setShouldSearchInBackground:NO]; [_tokenFieldView setShouldSortResults:NO]; @@ -41,11 +42,23 @@ - (void)viewDidLoad { [_tokenFieldView.tokenField setPromptText:@"To:"]; [_tokenFieldView.tokenField setPlaceholder:@"Type a name"]; + // ***** UIView contains : Search Button + Add Button + UIView* rightView=[[UIView alloc]initWithFrame:CGRectMake(0, 0, 100, 26)]; + UIButton * addButton = [UIButton buttonWithType:UIButtonTypeContactAdd]; [addButton addTarget:self action:@selector(showContactsPicker:) forControlEvents:UIControlEventTouchUpInside]; - [_tokenFieldView.tokenField setRightView:addButton]; + [_tokenFieldView.tokenField setRightView:rightView]; [_tokenFieldView.tokenField addTarget:self action:@selector(tokenFieldChangedEditing:) forControlEvents:UIControlEventEditingDidBegin]; [_tokenFieldView.tokenField addTarget:self action:@selector(tokenFieldChangedEditing:) forControlEvents:UIControlEventEditingDidEnd]; + + + // ***** Search button + UIButton *searchButton=[UIButton buttonWithType:UIButtonTypeDetailDisclosure]; + [searchButton addTarget:self action:@selector(searchUsers:) forControlEvents:UIControlEventTouchUpInside]; + [rightView addSubview:searchButton]; + [rightView addSubview:addButton]; + searchButton.frame=CGRectMake(0, 0, 24, 26); + addButton.frame=CGRectMake(48, 0, 26, 26); _messageView = [[UITextView alloc] initWithFrame:_tokenFieldView.contentView.bounds]; [_messageView setScrollEnabled:NO]; @@ -62,7 +75,10 @@ - (void)viewDidLoad { // They both do the same thing. [_tokenFieldView becomeFirstResponder]; } - +-(void)searchUsers:(UIButton*)sender{ + NSString *string=_tokenFieldView.tokenField.text; + [_tokenFieldView manualSearch:string]; +} - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation { return (toInterfaceOrientation != UIInterfaceOrientationPortraitUpsideDown); } From 083235e01c1e9cae445857533fabb0cac5b5d3b3 Mon Sep 17 00:00:00 2001 From: steven yang Date: Wed, 27 Nov 2013 16:05:28 +0800 Subject: [PATCH 3/6] Transparentize background color It is conventional to design a clear background color for UI component.But it's not easy to archive this with TITokenField.This commit worked fine. --- TITokenField.h | 1 + TITokenField.m | 12 ++++++++++++ .../Classes/TokenFieldExampleViewController.m | 5 +++-- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/TITokenField.h b/TITokenField.h index 4a4b240..f3af6ad 100644 --- a/TITokenField.h +++ b/TITokenField.h @@ -72,6 +72,7 @@ - (void)updateContentSize; -(id)initWithFrame:(CGRect)frame withFieldHeight:(float)_height; -(void)manualSearch:(NSString*)searchString; +-(void)transparentizeBackground; @end //========================================================== diff --git a/TITokenField.m b/TITokenField.m index 5e231f7..b7fbf71 100644 --- a/TITokenField.m +++ b/TITokenField.m @@ -9,6 +9,7 @@ #import "TITokenField.h" #import #import "NSString+truncateToSize.h" +#import @interface TITokenField () @property (nonatomic, assign) BOOL forcePickSearchResult; @@ -401,6 +402,16 @@ -(void)manualSearch:(NSString*)searchString{ [self setSearchResultsVisible:YES]; } } +-(void)transparentizeBackground{ + self.backgroundColor=[UIColor clearColor]; + self.tokenField.backgroundColor=[UIColor clearColor]; + self.tokenField.leftView.backgroundColor=[UIColor clearColor]; + + [self.tokenField.layer setShadowColor:[[UIColor clearColor] CGColor]]; + [self.tokenField.layer setShadowOpacity:1]; + [self.tokenField.layer setShadowRadius:0]; + +} - (NSString *)description { return [NSString stringWithFormat:@"", self, self.tokenTitles.count]; } @@ -876,6 +887,7 @@ - (void)setPlaceholder:(NSString *)placeholder { if (!label || ![label isKindOfClass:[UILabel class]]){ label = [[UILabel alloc] initWithFrame:CGRectMake(_tokenCaret.x + 3, _tokenCaret.y + 2, self.rightView.bounds.size.width, self.rightView.bounds.size.height)]; [label setTextColor:[UIColor colorWithWhite:0.75 alpha:1]]; + label.backgroundColor=[UIColor clearColor]; _placeHolderLabel = label; [self addSubview: _placeHolderLabel]; } diff --git a/TokenFieldExample/Classes/TokenFieldExampleViewController.m b/TokenFieldExample/Classes/TokenFieldExampleViewController.m index d366412..a4f47a1 100644 --- a/TokenFieldExample/Classes/TokenFieldExampleViewController.m +++ b/TokenFieldExample/Classes/TokenFieldExampleViewController.m @@ -26,13 +26,14 @@ - (void)viewDidLoad { self.edgesForExtendedLayout = UIRectEdgeNone; #endif - [self.view setBackgroundColor:[UIColor whiteColor]]; + [self.view setBackgroundColor:[UIColor lightGrayColor]]; [self.navigationItem setTitle:@"Example"]; _tokenFieldView = [[TITokenFieldView alloc] initWithFrame:self.view.bounds withFieldHeight:25]; [_tokenFieldView setSourceArray:[Names listOfNames]]; [self.view addSubview:_tokenFieldView]; [_tokenFieldView setStopAutoSearch:YES]; + [_tokenFieldView transparentizeBackground]; [_tokenFieldView.tokenField setDelegate:self]; [_tokenFieldView setShouldSearchInBackground:NO]; @@ -57,7 +58,7 @@ - (void)viewDidLoad { [searchButton addTarget:self action:@selector(searchUsers:) forControlEvents:UIControlEventTouchUpInside]; [rightView addSubview:searchButton]; [rightView addSubview:addButton]; - searchButton.frame=CGRectMake(0, 0, 24, 26); + searchButton.frame=CGRectMake(0, 0, 26, 26); addButton.frame=CGRectMake(48, 0, 26, 26); _messageView = [[UITextView alloc] initWithFrame:_tokenFieldView.contentView.bounds]; From 81b244af05ce301e397f4370b7b7bcbb2275da88 Mon Sep 17 00:00:00 2001 From: steven yang Date: Thu, 28 Nov 2013 09:44:05 +0800 Subject: [PATCH 4/6] Fixed: presentPopoverFromRect:inView: causes SIGABRT Steps to reproduce: 1. Run TokenFieldExamle in iPad 2. Touches the search button without type anything 3. App crashed,see the console Commit 'forbit auto-searching'(SHA:81311ee01656fc44202acbaa0c96e238bffcaac2 ) causes this bug. This commit fixed it. --- TITokenField.m | 1 + 1 file changed, 1 insertion(+) diff --git a/TITokenField.m b/TITokenField.m index b7fbf71..a6b205c 100644 --- a/TITokenField.m +++ b/TITokenField.m @@ -391,6 +391,7 @@ - (void)presentpopoverAtTokenFieldCaretAnimated:(BOOL)animated { UITextPosition * position = [_tokenField positionFromPosition:_tokenField.beginningOfDocument offset:2]; + position=position?position:_tokenField.beginningOfDocument; [_popoverController presentPopoverFromRect:[_tokenField caretRectForPosition:position] inView:_tokenField permittedArrowDirections:UIPopoverArrowDirectionUp animated:animated]; } From 10ca75bf031950898c5a42d1a5849e80da968296 Mon Sep 17 00:00:00 2001 From: steven yang Date: Thu, 28 Nov 2013 14:37:00 +0800 Subject: [PATCH 5/6] Token touches callback This commit allow to define a touch up inside callback function for specific token. --- TITokenField.m | 7 ++++++- .../Classes/TokenFieldExampleViewController.m | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/TITokenField.m b/TITokenField.m index a6b205c..e0a98fc 100644 --- a/TITokenField.m +++ b/TITokenField.m @@ -679,7 +679,12 @@ - (void)addToken:(TIToken *)token { //[self becomeFirstResponder]; [token addTarget:self action:@selector(tokenTouchDown:) forControlEvents:UIControlEventTouchDown]; - [token addTarget:self action:@selector(tokenTouchUpInside:) forControlEvents:UIControlEventTouchUpInside]; + if ([delegate respondsToSelector:@selector(tokenTouchUpInside:)]) { + [token addTarget:delegate action:@selector(tokenTouchUpInside:) + forControlEvents:UIControlEventTouchUpInside]; + }else{ + [token addTarget:self action:@selector(tokenTouchUpInside:) forControlEvents:UIControlEventTouchUpInside]; + } [self addSubview:token]; if (![_tokens containsObject:token]) { diff --git a/TokenFieldExample/Classes/TokenFieldExampleViewController.m b/TokenFieldExample/Classes/TokenFieldExampleViewController.m index a4f47a1..bc9c3c8 100644 --- a/TokenFieldExample/Classes/TokenFieldExampleViewController.m +++ b/TokenFieldExample/Classes/TokenFieldExampleViewController.m @@ -91,7 +91,9 @@ - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInte - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation { [self resizeViews]; } - +- (void)tokenTouchUpInside:(TIToken *)token{ + NSLog(@"Touches token:%@,%@",token.title,token.representedObject); +} - (void)showContactsPicker:(id)sender { // Show some kind of contacts picker in here. From 436896a545511c96a9dddb398b29207062884ad2 Mon Sep 17 00:00:00 2001 From: steven yang Date: Fri, 6 Dec 2013 15:13:48 +0800 Subject: [PATCH 6/6] Fixed: in iOS 7 set forcePickSearchResult to YES cause keyboard never dismissed! Steps to reproduce: 1. Open TokenFieldExample project, add this line to viewDidLoad method in TokenFieldExampleViewController.m: [_tokenFieldView setForcePickSearchResult:YES]; 2. Run TokenFieldExample on iPad(not simulator). 3. Touch the token field and do not input any word. 4. The keyboard popup, but click the "dismiss keyboard button" nothing happening. If run on iOS 6, "dismiss keyboard button" just work fine.But on iOS 7,the keyboard does not disappear, it just stays there. The user can still navigate through back navigation bar button, but all text fields are disabled, meaning you can't enter text anywhere. The only option the user has is killing the app and starting fresh. Try to send resignFirstResponder messages, but no help anything. This commit fixed it. --- TITokenField.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/TITokenField.m b/TITokenField.m index e0a98fc..467534f 100644 --- a/TITokenField.m +++ b/TITokenField.m @@ -622,9 +622,9 @@ - (void)didEndEditing { } [self setResultsModeEnabled:NO]; - if (_tokens.count < 1 && self.forcePickSearchResult) { - [self becomeFirstResponder]; - } +// if (_tokens.count < 1 && self.forcePickSearchResult) { +// [self becomeFirstResponder]; +// } } - (void)didChangeText {