diff --git a/JSTokenField/JSTokenButton.h b/JSTokenButton.h similarity index 79% rename from JSTokenField/JSTokenButton.h rename to JSTokenButton.h index 1223204..9c29cfa 100644 --- a/JSTokenField/JSTokenButton.h +++ b/JSTokenButton.h @@ -26,29 +26,23 @@ // or implied, of James Addyman (JamSoft). // + #import -@class JSTokenField; -@interface JSTokenButton : UIButton { - BOOL _toggled; - - UIImage *_normalBg; - UIImage *_highlightedBg; - - id _representedObject; - -} +@class JSTokenField; -@property (nonatomic, getter=isToggled) BOOL toggled; -@property (nonatomic, retain) UIImage *normalBg; -@property (nonatomic, retain) UIImage *highlightedBg; +@interface JSTokenButton : UIButton -@property (nonatomic, retain) id representedObject; +@property (nonatomic, retain) id identifier; +@property (nonatomic, getter=isActive) BOOL active; @property (nonatomic, assign) JSTokenField *parentField; +@property (nonatomic, retain) UIImage *normalBackgroundImage, *highlightedBackgroundImage; + + ++ (JSTokenButton *)tokenWithLabel:(NSString *)labelText forIdentifier:(id)identifier; -+ (JSTokenButton *)tokenWithString:(NSString *)string representedObject:(id)obj; @end diff --git a/JSTokenField/JSTokenButton.m b/JSTokenButton.m similarity index 51% rename from JSTokenField/JSTokenButton.m rename to JSTokenButton.m index f20112b..783296c 100644 --- a/JSTokenField/JSTokenButton.m +++ b/JSTokenButton.m @@ -26,99 +26,112 @@ // or implied, of James Addyman (JamSoft). // + #import "JSTokenButton.h" #import "JSTokenField.h" #import + @implementation JSTokenButton -@synthesize toggled = _toggled; -@synthesize normalBg = _normalBg; -@synthesize highlightedBg = _highlightedBg; -@synthesize representedObject = _representedObject; -@synthesize parentField = _parentField; -+ (JSTokenButton *)tokenWithString:(NSString *)string representedObject:(id)obj ++ (JSTokenButton *)tokenWithLabel:(NSString *)labelText forIdentifier:(id)identifier { - JSTokenButton *button = (JSTokenButton *)[self buttonWithType:UIButtonTypeCustom]; - [button setNormalBg:[[UIImage imageNamed:@"tokenNormal.png"] stretchableImageWithLeftCapWidth:14 topCapHeight:0]]; - [button setHighlightedBg:[[UIImage imageNamed:@"tokenHighlighted.png"] stretchableImageWithLeftCapWidth:14 topCapHeight:0]]; - [button setAdjustsImageWhenHighlighted:NO]; - [button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; - [[button titleLabel] setFont:[UIFont fontWithName:@"Helvetica Neue" size:15]]; - [[button titleLabel] setLineBreakMode:UILineBreakModeTailTruncation]; - [button setTitleEdgeInsets:UIEdgeInsetsMake(2, 10, 0, 10)]; + JSTokenButton *token = (JSTokenButton *)[self buttonWithType:UIButtonTypeCustom]; + token.identifier = identifier; + token.active = FALSE; + + // Set the background appearance + token.adjustsImageWhenHighlighted = FALSE; + token.normalBackgroundImage = [[UIImage imageNamed:@"tokenNormal.png"] stretchableImageWithLeftCapWidth:14 topCapHeight:0]; + token.highlightedBackgroundImage = [[UIImage imageNamed:@"tokenHighlighted.png"] stretchableImageWithLeftCapWidth:14 topCapHeight:0]; + + // Style the buttons appearance + [token setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; + [token.titleLabel setFont:[UIFont fontWithName:@"Helvetica Neue" size:15]]; + [token.titleLabel setLineBreakMode:NSLineBreakByTruncatingTail]; + [token setTitleEdgeInsets:UIEdgeInsetsMake(2, 10, 0, 10)]; + [token setTitle:labelText forState:UIControlStateNormal]; - [button setTitle:string forState:UIControlStateNormal]; + // Adjust the tokens frame + [token sizeToFit]; - [button sizeToFit]; - CGRect frame = [button frame]; + CGRect frame = [token frame]; frame.size.width += 20; frame.size.height = 25; - [button setFrame:frame]; + token.frame = frame; - [button setToggled:NO]; + [token updateAppearance]; - [button setRepresentedObject:obj]; - - return button; + return token; } -- (void)setToggled:(BOOL)toggled +- (void)setActive:(BOOL)active { - _toggled = toggled; + _active = active; - if (_toggled) - { - [self setBackgroundImage:self.highlightedBg forState:UIControlStateNormal]; + [self updateAppearance]; +} + + +- (void)updateAppearance +{ + if([self isActive]) { + [self setBackgroundImage:self.highlightedBackgroundImage forState:UIControlStateNormal]; [self setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; - } - else - { - [self setBackgroundImage:self.normalBg forState:UIControlStateNormal]; + } else { + [self setBackgroundImage:self.normalBackgroundImage forState:UIControlStateNormal]; [self setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; } } -- (void)dealloc + +- (BOOL)canBecomeFirstResponder { - self.representedObject = nil; - self.highlightedBg = nil; - self.normalBg = nil; - [super dealloc]; + return YES; } -- (BOOL)becomeFirstResponder { - BOOL superReturn = [super becomeFirstResponder]; - if (superReturn) { - self.toggled = YES; - } - return superReturn; + +- (BOOL)becomeFirstResponder +{ + BOOL shouldBecomeFirstResponder = [super becomeFirstResponder]; + if(shouldBecomeFirstResponder) + self.active = TRUE; + + return shouldBecomeFirstResponder; } -- (BOOL)resignFirstResponder { - BOOL superReturn = [super resignFirstResponder]; - if (superReturn) { - self.toggled = NO; - } - return superReturn; + +- (BOOL)resignFirstResponder +{ + BOOL shouldResignFirstResponder = [super resignFirstResponder]; + if(shouldResignFirstResponder) + self.active = FALSE; + + return shouldResignFirstResponder; } + + #pragma mark - UIKeyInput -- (void)deleteBackward { - [_parentField removeTokenForString:[self titleForState:UIControlStateNormal]]; + +- (void)deleteBackward +{ + [self.parentField becomeFirstResponder]; + [self.parentField removeTokenForIdentifier:self.identifier]; } -- (BOOL)hasText { + +- (BOOL)hasText +{ return NO; } -- (void)insertText:(NSString *)text { - return; -} -- (BOOL)canBecomeFirstResponder { - return YES; +- (void)insertText:(NSString *)text +{ } + + @end diff --git a/JSTokenField/JSTokenField.h b/JSTokenField.h similarity index 62% rename from JSTokenField/JSTokenField.h rename to JSTokenField.h index 415ef05..ba6246e 100644 --- a/JSTokenField/JSTokenField.h +++ b/JSTokenField.h @@ -28,46 +28,38 @@ #import -@class JSTokenButton; -@protocol JSTokenFieldDelegate; - -extern NSString *const JSTokenFieldFrameDidChangeNotification; -extern NSString *const JSTokenFieldNewFrameKey; -extern NSString *const JSTokenFieldOldFrameKey; -extern NSString *const JSDeletedTokenKey; - -@interface JSTokenField : UIView { - - NSMutableArray *_tokens; - - UITextField *_textField; - - id _delegate; - - JSTokenButton *_deletedToken; - - UILabel *_label; -} - -@property (nonatomic, readonly) UITextField *textField; -@property (nonatomic, retain) UILabel *label; -@property (nonatomic, readonly, copy) NSMutableArray *tokens; -@property (nonatomic, assign) id delegate; - -- (void)addTokenWithTitle:(NSString *)string representedObject:(id)obj; -- (void)removeTokenForString:(NSString *)string; -- (void)removeTokenWithRepresentedObject:(id)representedObject; +@class JSTokenField; -@end @protocol JSTokenFieldDelegate - @optional -- (void)tokenField:(JSTokenField *)tokenField didAddToken:(NSString *)title representedObject:(id)obj; -- (void)tokenField:(JSTokenField *)tokenField didRemoveToken:(NSString *)title representedObject:(id)obj; +- (NSArray *)tokenField:(JSTokenField *)tokenField tokenIdentifiersForString:(NSString *)untokenizedText; +- (NSString *)tokenField:(JSTokenField *)tokenField labelForIdentifier:(id)identifier; -- (BOOL)tokenFieldShouldReturn:(JSTokenField *)tokenField; +- (void)tokenField:(JSTokenField *)tokenField didAddTokenWithIdentifier:(id)identifier; +- (void)tokenField:(JSTokenField *)tokenField didRemoveTokenWithIdentifier:(id)identifier; - (void)tokenFieldDidEndEditing:(JSTokenField *)tokenField; @end + + + +@interface JSTokenField : UIView + +@property (nonatomic, assign) id delegate; +@property (nonatomic, readonly, retain) UILabel *label; +@property (nonatomic, readonly, retain) UITextField *textField; + +@property (nonatomic, assign) UIEdgeInsets contentInsets; +@property (nonatomic, assign) CGSize tokenPadding; + +- (NSArray *)allTokens; +- (void)addTokenIdentifiers:(NSArray *)tokenIdentifiers; +- (void)removeAllTokens; + +- (void)addTokenWithLabel:(NSString *)labelText forIdentifier:(id)identifier; +- (void)removeTokenForIdentifier:(id)identifier; + +@end + diff --git a/JSTokenField.m b/JSTokenField.m new file mode 100644 index 0000000..2a250f4 --- /dev/null +++ b/JSTokenField.m @@ -0,0 +1,414 @@ +// +// Copyright 2011 James Addyman (JamSoft). All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other materials +// provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY JAMES ADDYMAN (JAMSOFT) ``AS IS'' AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JAMES ADDYMAN (JAMSOFT) OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// The views and conclusions contained in the software and documentation are those of the +// authors and should not be interpreted as representing official policies, either expressed +// or implied, of James Addyman (JamSoft). +// + +#import "JSTokenField.h" +#import "JSTokenButton.h" +#import + +NSString * const JSZeroWidthSpaceString = @"\u200B"; + + +@interface JSTokenField () + +@property (nonatomic, strong) NSMutableArray *tokens; +@property (nonatomic, readwrite, retain) UITextField *textField; +@property (nonatomic, readwrite, retain) UIScrollView *scrollView; + +@end + + + +@implementation JSTokenField + + +- (id)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + + if(self) { + [self setup]; + } + + return self; +} + + +- (id)initWithCoder:(NSCoder *)aDecoder +{ + self = [super initWithCoder:aDecoder]; + + if(self) { + [self setup]; + } + + return self; +} + + +- (void)setup +{ + self.tokens = [[NSMutableArray alloc] init]; + + // Setup the fields appearance views + self.clipsToBounds = TRUE; + [self setBackgroundColor:[UIColor colorWithRed:0.9 green:0.9 blue:0.9 alpha:1.0]]; + + CGRect frame = self.frame; + + UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:self.bounds]; + self.scrollView = scrollView; + [self addSubview:scrollView]; + + UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 3.0, 8.0, 3.0); + self.tokenPadding = CGSizeMake(5, 5); + self.contentInsets = contentInsets; + + frame.size.height -= contentInsets.top + contentInsets.bottom; + + + UITextField *textField = [[UITextField alloc] initWithFrame:frame]; + [textField setContentVerticalAlignment:UIControlContentVerticalAlignmentTop]; + textField.delegate = self; + textField.text = JSZeroWidthSpaceString; + + [scrollView addSubview:textField]; + self.textField = textField; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleTextDidChange:) name:UITextFieldTextDidChangeNotification object:textField]; +} + + + +#pragma mark - UIResponder methods + + +- (BOOL)canBecomeFirstResponder +{ + return TRUE; +} + + +- (BOOL)canResignFirstResponder +{ + return TRUE; +} + + +- (BOOL)becomeFirstResponder +{ + return [self.textField becomeFirstResponder]; +} + + +- (BOOL)resignFirstResponder +{ + [super resignFirstResponder]; + [self.textField resignFirstResponder]; + + for(JSTokenButton *token in self.tokens) { + [token resignFirstResponder]; + } + + return TRUE; +} + + +- (void)layoutSubviews +{ + self.scrollView.frame = self.bounds; + + CGRect currentRect = CGRectMake(_contentInsets.left, 0.0, 0.0, 0.0); + + for(UIButton *token in self.tokens) { + CGRect tokenFrame = [token frame]; + + if((currentRect.origin.x + tokenFrame.size.width) > self.frame.size.width - _contentInsets.right) + currentRect.origin = CGPointMake(_contentInsets.left, (currentRect.origin.y + tokenFrame.size.height + _tokenPadding.height)); + + tokenFrame.origin.x = currentRect.origin.x; + tokenFrame.origin.y = currentRect.origin.y + _contentInsets.top - 1; + token.frame = tokenFrame; + + if(![token superview]) + [self.scrollView addSubview:token]; + + currentRect.origin.x += tokenFrame.size.width + _tokenPadding.width; + currentRect.size = tokenFrame.size; + } + + + CGRect textFieldFrame = [self.textField frame]; + textFieldFrame.origin = currentRect.origin; + + if((self.frame.size.width - textFieldFrame.origin.x) >= 60) { + textFieldFrame.size.width = self.frame.size.width - textFieldFrame.origin.x - _contentInsets.right; + } else { + textFieldFrame.size.width = self.frame.size.width - _contentInsets.right; + textFieldFrame.origin = CGPointMake(_contentInsets.left, (currentRect.origin.y + currentRect.size.height + _contentInsets.top)); + } + + textFieldFrame.origin.y += _contentInsets.top; + self.textField.frame = textFieldFrame; + + self.scrollView.contentSize = CGSizeMake(self.frame.size.width, CGRectGetMaxY(currentRect) + 30.0 + _contentInsets.bottom + _tokenPadding.height); +} + + + +#pragma mark - Managing tokens + + +- (NSArray *)allTokens +{ + return [self.tokens copy]; +} + + +- (void)addTokenIdentifiers:(NSArray *)tokenIdentifiers +{ + for(id identifiers in tokenIdentifiers) { + NSString *labelText = [self labelForTokenIdentifier:identifiers]; + + [self addTokenWithLabel:labelText forIdentifier:identifiers]; + } +} + + +- (void)removeAllTokens +{ + for(JSTokenButton *token in [self allTokens]) { + [self removeToken:token]; + } +} + + +- (void)addTokenWithLabel:(NSString *)labelText forIdentifier:(id)identifier +{ + if([labelText length] == 0) + return; + + self.textField.text = JSZeroWidthSpaceString; + labelText = [labelText stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + + if([labelText length]) { + JSTokenButton *token = [JSTokenButton tokenWithLabel:labelText forIdentifier:identifier]; + token.parentField = self; + + CGRect frame = [token frame]; + + if(frame.size.width > self.frame.size.width) + frame.size.width = self.frame.size.width - _contentInsets.left - _contentInsets.right; + + token.frame = frame; + [token addTarget:self action:@selector(selectToken:) forControlEvents:UIControlEventTouchUpInside]; + [self.tokens addObject:token]; + + if([self.delegate respondsToSelector:@selector(tokenField:didAddTokenWithIdentifier:)]) + [self.delegate tokenField:self didAddTokenWithIdentifier:identifier]; + + [self setNeedsLayout]; + } + + [self scrollToBottom]; +} + + +- (void)removeToken:(JSTokenButton *)tokenToRemove +{ + if(!tokenToRemove) + return; + + if([tokenToRemove isFirstResponder]) + [self.textField becomeFirstResponder]; + + [tokenToRemove removeFromSuperview]; + [self.tokens removeObject:tokenToRemove]; + + if([self.delegate respondsToSelector:@selector(tokenField:didRemoveTokenWithIdentifier:)]) + [self.delegate tokenField:self didRemoveTokenWithIdentifier:tokenToRemove.identifier]; + + [self setNeedsLayout]; +} + + +- (void)removeTokenForIdentifier:(id)identifier +{ + if(!identifier) + return; + + for(JSTokenButton *token in self.tokens) { + if([token.identifier isEqual:identifier]) { + [self removeToken:token]; + break; + } + } +} + + +- (void)deleteActiveToken +{ + for(JSTokenButton *token in self.tokens) { + if([token isActive]) { + [self removeToken:token]; + break; + } + } +} + + +- (void)selectToken:(JSTokenButton *)tokenToSelect +{ + for(JSTokenButton *token in self.tokens) { + token.active = FALSE; + } + + tokenToSelect.active = TRUE; + [tokenToSelect becomeFirstResponder]; +} + + + +#pragma mark - +#pragma mark Interacting with the delegate + + +- (void)handleTextDidChange:(NSNotification *)note +{ + // Ensure there's always a space at the beginning + NSMutableString *text = self.textField.text.mutableCopy; + + if(![text hasPrefix:JSZeroWidthSpaceString]) { + [text insertString:JSZeroWidthSpaceString atIndex:0]; + self.textField.text = text; + } + + [self scrollToBottom]; +} + + +- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string +{ + if([string isEqualToString:@""] + && (NSEqualRanges(range, NSMakeRange(0, 0)) + || [[self.textField.text substringWithRange:range] isEqualToString:JSZeroWidthSpaceString])) + { + JSTokenButton *token = [self.tokens lastObject]; + [token becomeFirstResponder]; + return NO; + } + + [self scrollToBottom]; + return YES; +} + + +- (BOOL)textFieldShouldReturn:(UITextField *)textField +{ + [self askDelegateToTokenizeText]; + [self scrollToBottom]; + + return FALSE; +} + + +- (void)textFieldDidEndEditing:(UITextField *)textField +{ + [self askDelegateToTokenizeText]; + [self scrollToBottom]; + + if([self.delegate respondsToSelector:@selector(tokenFieldDidEndEditing:)]) + [self.delegate tokenFieldDidEndEditing:self]; +} + + +- (void)scrollToBottom +{ + UIScrollView *scrollView = self.scrollView; + + CGFloat height = 10.0; + CGSize contentSize = [scrollView contentSize]; + CGRect scrollRect = CGRectMake(0.0, contentSize.height + _contentInsets.top + _contentInsets.bottom + 20.0, contentSize.width, height); + + [CATransaction begin]; + [CATransaction setValue:[NSNumber numberWithBool:TRUE] forKey:kCATransactionDisableActions]; + [scrollView scrollRectToVisible:scrollRect animated:FALSE]; + [CATransaction commit]; +} + + +- (void)askDelegateToTokenizeText +{ + NSString *untokenizedText = self.textField.text; + NSArray *newTokenIdentifiers = nil; + + if([self.delegate respondsToSelector:@selector(tokenField:tokenIdentifiersForString:)]) { + // Ask the delegate to tokenize the given string + newTokenIdentifiers = [self.delegate tokenField:self tokenIdentifiersForString:untokenizedText]; + } else { + // Otherwise treat tokens as any characters seperated by a space + newTokenIdentifiers = [untokenizedText componentsSeparatedByString:@" "]; + } + + for(id tokenIdentifier in newTokenIdentifiers) { + if([tokenIdentifier length] == 0) + continue; + + NSString *labelText = [self labelForTokenIdentifier:tokenIdentifier]; + [self addTokenWithLabel:labelText forIdentifier:tokenIdentifier]; + } +} + + +- (NSString *)labelForTokenIdentifier:(id)identifier +{ + NSString *labelText = nil; + + if([self.delegate respondsToSelector:@selector(tokenField:labelForIdentifier:)]) + labelText = [self.delegate tokenField:self labelForIdentifier:identifier]; + + // Otherwise if its a string use the tokenIdentifier for the tokens label + if(!labelText) + labelText = identifier; + + // Make sure that the label is a string + if([labelText isKindOfClass:[NSString class]] && [labelText length] > 0) + return labelText; + + return nil; +} + + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + + +@end + + diff --git a/JSTokenField.xcodeproj/project.pbxproj b/JSTokenField.xcodeproj/project.pbxproj index 23652b8..c56c83c 100644 --- a/JSTokenField.xcodeproj/project.pbxproj +++ b/JSTokenField.xcodeproj/project.pbxproj @@ -12,9 +12,6 @@ 1A1A069F13FEDEEC00CA6645 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A1A069E13FEDEEC00CA6645 /* CoreGraphics.framework */; }; 1A1A06A513FEDEEC00CA6645 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1A1A06A313FEDEEC00CA6645 /* InfoPlist.strings */; }; 1A1A06A713FEDEEC00CA6645 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A1A06A613FEDEEC00CA6645 /* main.m */; }; - 1A1A06AB13FEDEEC00CA6645 /* DemoAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A1A06AA13FEDEEC00CA6645 /* DemoAppDelegate.m */; }; - 1A1A06AE13FEDEEC00CA6645 /* DemoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A1A06AD13FEDEEC00CA6645 /* DemoViewController.m */; }; - 1A1A06B113FEDEEC00CA6645 /* DemoViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1A1A06AF13FEDEEC00CA6645 /* DemoViewController.xib */; }; 1A1A06BB13FEE01C00CA6645 /* JSTokenButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A1A06B813FEE01C00CA6645 /* JSTokenButton.m */; }; 1A1A06BC13FEE01C00CA6645 /* JSTokenField.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A1A06BA13FEE01C00CA6645 /* JSTokenField.m */; }; 1A1A06C113FEE2D900CA6645 /* tokenHighlighted.png in Resources */ = {isa = PBXBuildFile; fileRef = 1A1A06BD13FEE2D900CA6645 /* tokenHighlighted.png */; }; @@ -23,6 +20,8 @@ 1A1A06C413FEE2D900CA6645 /* tokenNormal@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1A1A06C013FEE2D900CA6645 /* tokenNormal@2x.png */; }; 1A1A06CF13FF000C00CA6645 /* AddressBook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A1A06CD13FF000C00CA6645 /* AddressBook.framework */; }; 1A1A06D013FF000C00CA6645 /* AddressBookUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A1A06CE13FF000C00CA6645 /* AddressBookUI.framework */; }; + 4A3D75BE169735DB000AB0B5 /* DemoAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A3D75BB169735DB000AB0B5 /* DemoAppDelegate.m */; }; + 4A3D75BF169735DB000AB0B5 /* DemoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A3D75BD169735DB000AB0B5 /* DemoViewController.m */; }; B71650D1144399F800EBF2C7 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A1A069C13FEDEEC00CA6645 /* Foundation.framework */; }; B71650DB14439A0500EBF2C7 /* JSTokenField.h in Headers */ = {isa = PBXBuildFile; fileRef = 1A1A06B913FEE01C00CA6645 /* JSTokenField.h */; }; B71650DC14439A0500EBF2C7 /* JSTokenField.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A1A06BA13FEE01C00CA6645 /* JSTokenField.m */; }; @@ -39,11 +38,6 @@ 1A1A06A413FEDEEC00CA6645 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 1A1A06A613FEDEEC00CA6645 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 1A1A06A813FEDEEC00CA6645 /* JSTokenField-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JSTokenField-Prefix.pch"; sourceTree = ""; }; - 1A1A06A913FEDEEC00CA6645 /* DemoAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DemoAppDelegate.h; sourceTree = ""; }; - 1A1A06AA13FEDEEC00CA6645 /* DemoAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DemoAppDelegate.m; sourceTree = ""; }; - 1A1A06AC13FEDEEC00CA6645 /* DemoViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DemoViewController.h; sourceTree = ""; }; - 1A1A06AD13FEDEEC00CA6645 /* DemoViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DemoViewController.m; sourceTree = ""; }; - 1A1A06B013FEDEEC00CA6645 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/DemoViewController.xib; sourceTree = ""; }; 1A1A06B713FEE01C00CA6645 /* JSTokenButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSTokenButton.h; sourceTree = ""; }; 1A1A06B813FEE01C00CA6645 /* JSTokenButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSTokenButton.m; sourceTree = ""; }; 1A1A06B913FEE01C00CA6645 /* JSTokenField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSTokenField.h; sourceTree = ""; }; @@ -54,6 +48,10 @@ 1A1A06C013FEE2D900CA6645 /* tokenNormal@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "tokenNormal@2x.png"; sourceTree = ""; }; 1A1A06CD13FF000C00CA6645 /* AddressBook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBook.framework; path = System/Library/Frameworks/AddressBook.framework; sourceTree = SDKROOT; }; 1A1A06CE13FF000C00CA6645 /* AddressBookUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBookUI.framework; path = System/Library/Frameworks/AddressBookUI.framework; sourceTree = SDKROOT; }; + 4A3D75BA169735DB000AB0B5 /* DemoAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DemoAppDelegate.h; path = "Demo App/DemoAppDelegate.h"; sourceTree = SOURCE_ROOT; }; + 4A3D75BB169735DB000AB0B5 /* DemoAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DemoAppDelegate.m; path = "Demo App/DemoAppDelegate.m"; sourceTree = SOURCE_ROOT; }; + 4A3D75BC169735DB000AB0B5 /* DemoViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DemoViewController.h; path = "Demo App/DemoViewController.h"; sourceTree = SOURCE_ROOT; }; + 4A3D75BD169735DB000AB0B5 /* DemoViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DemoViewController.m; path = "Demo App/DemoViewController.m"; sourceTree = SOURCE_ROOT; }; B71650D0144399F800EBF2C7 /* libJSTokenFieldLibrary.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libJSTokenFieldLibrary.a; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -84,7 +82,9 @@ 1A1A068B13FEDEEC00CA6645 = { isa = PBXGroup; children = ( + 4A3D75C216973601000AB0B5 /* Demo App Controller */, 1A1A06A013FEDEEC00CA6645 /* JSTokenField */, + 1A1A06A113FEDEEC00CA6645 /* Supporting Files */, 1A1A069913FEDEEC00CA6645 /* Frameworks */, 1A1A069713FEDEEC00CA6645 /* Products */, ); @@ -114,16 +114,10 @@ 1A1A06A013FEDEEC00CA6645 /* JSTokenField */ = { isa = PBXGroup; children = ( - 1A1A06A913FEDEEC00CA6645 /* DemoAppDelegate.h */, - 1A1A06AA13FEDEEC00CA6645 /* DemoAppDelegate.m */, - 1A1A06AC13FEDEEC00CA6645 /* DemoViewController.h */, - 1A1A06AD13FEDEEC00CA6645 /* DemoViewController.m */, 1A1A06B913FEE01C00CA6645 /* JSTokenField.h */, 1A1A06BA13FEE01C00CA6645 /* JSTokenField.m */, 1A1A06B713FEE01C00CA6645 /* JSTokenButton.h */, 1A1A06B813FEE01C00CA6645 /* JSTokenButton.m */, - 1A1A06AF13FEDEEC00CA6645 /* DemoViewController.xib */, - 1A1A06A113FEDEEC00CA6645 /* Supporting Files */, ); path = JSTokenField; sourceTree = ""; @@ -141,6 +135,18 @@ 1A1A06C013FEE2D900CA6645 /* tokenNormal@2x.png */, ); name = "Supporting Files"; + path = JSTokenField; + sourceTree = ""; + }; + 4A3D75C216973601000AB0B5 /* Demo App Controller */ = { + isa = PBXGroup; + children = ( + 4A3D75BA169735DB000AB0B5 /* DemoAppDelegate.h */, + 4A3D75BB169735DB000AB0B5 /* DemoAppDelegate.m */, + 4A3D75BC169735DB000AB0B5 /* DemoViewController.h */, + 4A3D75BD169735DB000AB0B5 /* DemoViewController.m */, + ); + name = "Demo App Controller"; sourceTree = ""; }; /* End PBXGroup section */ @@ -225,7 +231,6 @@ buildActionMask = 2147483647; files = ( 1A1A06A513FEDEEC00CA6645 /* InfoPlist.strings in Resources */, - 1A1A06B113FEDEEC00CA6645 /* DemoViewController.xib in Resources */, 1A1A06C113FEE2D900CA6645 /* tokenHighlighted.png in Resources */, 1A1A06C213FEE2D900CA6645 /* tokenHighlighted@2x.png in Resources */, 1A1A06C313FEE2D900CA6645 /* tokenNormal.png in Resources */, @@ -241,10 +246,10 @@ buildActionMask = 2147483647; files = ( 1A1A06A713FEDEEC00CA6645 /* main.m in Sources */, - 1A1A06AB13FEDEEC00CA6645 /* DemoAppDelegate.m in Sources */, - 1A1A06AE13FEDEEC00CA6645 /* DemoViewController.m in Sources */, 1A1A06BB13FEE01C00CA6645 /* JSTokenButton.m in Sources */, 1A1A06BC13FEE01C00CA6645 /* JSTokenField.m in Sources */, + 4A3D75BE169735DB000AB0B5 /* DemoAppDelegate.m in Sources */, + 4A3D75BF169735DB000AB0B5 /* DemoViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -268,14 +273,6 @@ name = InfoPlist.strings; sourceTree = ""; }; - 1A1A06AF13FEDEEC00CA6645 /* DemoViewController.xib */ = { - isa = PBXVariantGroup; - children = ( - 1A1A06B013FEDEEC00CA6645 /* en */, - ); - name = DemoViewController.xib; - sourceTree = ""; - }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ diff --git a/JSTokenField/DemoAppDelegate.h b/JSTokenField/DemoAppDelegate.h deleted file mode 100644 index f1c5da5..0000000 --- a/JSTokenField/DemoAppDelegate.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// Copyright 2011 James Addyman (JamSoft). All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other materials -// provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY JAMES ADDYMAN (JAMSOFT) ``AS IS'' AND ANY EXPRESS OR IMPLIED -// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JAMES ADDYMAN (JAMSOFT) OR -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// The views and conclusions contained in the software and documentation are those of the -// authors and should not be interpreted as representing official policies, either expressed -// or implied, of James Addyman (JamSoft). -// - -#import - -@class DemoViewController; - -@interface DemoAppDelegate : UIResponder - -@property (nonatomic, retain) UIWindow *window; - -@property (nonatomic, retain) DemoViewController *viewController; - -@end diff --git a/JSTokenField/DemoAppDelegate.m b/JSTokenField/DemoAppDelegate.m deleted file mode 100644 index 05a1d11..0000000 --- a/JSTokenField/DemoAppDelegate.m +++ /dev/null @@ -1,87 +0,0 @@ -// -// Copyright 2011 James Addyman (JamSoft). All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other materials -// provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY JAMES ADDYMAN (JAMSOFT) ``AS IS'' AND ANY EXPRESS OR IMPLIED -// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JAMES ADDYMAN (JAMSOFT) OR -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// The views and conclusions contained in the software and documentation are those of the -// authors and should not be interpreted as representing official policies, either expressed -// or implied, of James Addyman (JamSoft). -// - -#import "DemoAppDelegate.h" - -#import "DemoViewController.h" - -@implementation DemoAppDelegate - -@synthesize window = _window; -@synthesize viewController = _viewController; - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions -{ - self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; - // Override point for customization after application launch. - self.viewController = [[DemoViewController alloc] initWithNibName:@"DemoViewController" bundle:nil]; - self.window.rootViewController = self.viewController; - [self.window makeKeyAndVisible]; - return YES; -} - -- (void)applicationWillResignActive:(UIApplication *)application -{ - /* - Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. - */ -} - -- (void)applicationDidEnterBackground:(UIApplication *)application -{ - /* - Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. - */ -} - -- (void)applicationWillEnterForeground:(UIApplication *)application -{ - /* - Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. - */ -} - -- (void)applicationDidBecomeActive:(UIApplication *)application -{ - /* - Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. - */ -} - -- (void)applicationWillTerminate:(UIApplication *)application -{ - /* - Called when the application is about to terminate. - Save data if appropriate. - See also applicationDidEnterBackground:. - */ -} - -@end diff --git a/JSTokenField/DemoViewController.h b/JSTokenField/DemoViewController.h deleted file mode 100644 index 6ee57d2..0000000 --- a/JSTokenField/DemoViewController.h +++ /dev/null @@ -1,44 +0,0 @@ -// -// Copyright 2011 James Addyman (JamSoft). All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other materials -// provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY JAMES ADDYMAN (JAMSOFT) ``AS IS'' AND ANY EXPRESS OR IMPLIED -// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JAMES ADDYMAN (JAMSOFT) OR -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// The views and conclusions contained in the software and documentation are those of the -// authors and should not be interpreted as representing official policies, either expressed -// or implied, of James Addyman (JamSoft). -// - -#import -#import -#import -#import "JSTokenField.h" - -@interface DemoViewController : UIViewController { - - NSMutableArray *_toRecipients; - NSMutableArray *_ccRecipients; - - JSTokenField *_toField; - JSTokenField *_ccField; - -} - -@end diff --git a/JSTokenField/DemoViewController.m b/JSTokenField/DemoViewController.m deleted file mode 100644 index 08cc9ac..0000000 --- a/JSTokenField/DemoViewController.m +++ /dev/null @@ -1,163 +0,0 @@ -// -// Copyright 2011 James Addyman (JamSoft). All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other materials -// provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY JAMES ADDYMAN (JAMSOFT) ``AS IS'' AND ANY EXPRESS OR IMPLIED -// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JAMES ADDYMAN (JAMSOFT) OR -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// The views and conclusions contained in the software and documentation are those of the -// authors and should not be interpreted as representing official policies, either expressed -// or implied, of James Addyman (JamSoft). -// - -#import "DemoViewController.h" -#import "JSTokenField.h" - -@implementation DemoViewController - -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; - [_toRecipients release], _toRecipients = nil; - [_toField release], _toField = nil; - - [super dealloc]; -} - -- (void)didReceiveMemoryWarning -{ - [super didReceiveMemoryWarning]; -} - -#pragma mark - View lifecycle - -- (void)viewDidLoad -{ - [super viewDidLoad]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(handleTokenFieldFrameDidChange:) - name:JSTokenFieldFrameDidChangeNotification - object:nil]; - - _toRecipients = [[NSMutableArray alloc] init]; - - _toField = [[JSTokenField alloc] initWithFrame:CGRectMake(0, 0, 320, 31)]; - [[_toField label] setText:@"To:"]; - [_toField setDelegate:self]; - [self.view addSubview:_toField]; - - UIView *separator1 = [[[UIView alloc] initWithFrame:CGRectMake(0, _toField.bounds.size.height-1, _toField.bounds.size.width, 1)] autorelease]; - [separator1 setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin]; - [_toField addSubview:separator1]; - [separator1 setBackgroundColor:[UIColor lightGrayColor]]; - - _ccField = [[JSTokenField alloc] initWithFrame:CGRectMake(0, 31, 320, 31)]; - [[_ccField label] setText:@"CC:"]; - [_ccField setDelegate:self]; - [self.view addSubview:_ccField]; - - UIView *separator2 = [[[UIView alloc] initWithFrame:CGRectMake(0, _ccField.bounds.size.height-1, _ccField.bounds.size.width, 1)] autorelease]; - [separator2 setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin]; - [_ccField addSubview:separator2]; - [separator2 setBackgroundColor:[UIColor lightGrayColor]]; - -} - -- (void)viewDidUnload -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; - [_toRecipients release], _toRecipients = nil; - [_toField release], _toField = nil; - [super viewDidUnload]; -} - -- (void)viewWillAppear:(BOOL)animated -{ - [super viewWillAppear:animated]; -} - -- (void)viewDidAppear:(BOOL)animated -{ - [super viewDidAppear:animated]; -} - -- (void)viewWillDisappear:(BOOL)animated -{ - [super viewWillDisappear:animated]; -} - -- (void)viewDidDisappear:(BOOL)animated -{ - [super viewDidDisappear:animated]; -} - -#pragma mark - -#pragma mark JSTokenFieldDelegate - -- (void)tokenField:(JSTokenField *)tokenField didAddToken:(NSString *)title representedObject:(id)obj -{ - NSDictionary *recipient = [NSDictionary dictionaryWithObject:obj forKey:title]; - [_toRecipients addObject:recipient]; - NSLog(@"Added token for < %@ : %@ >\n%@", title, obj, _toRecipients); - -} - -- (void)tokenField:(JSTokenField *)tokenField didRemoveTokenAtIndex:(NSUInteger)index -{ - [_toRecipients removeObjectAtIndex:index]; - NSLog(@"Deleted token %d\n%@", index, _toRecipients); -} - -- (BOOL)tokenFieldShouldReturn:(JSTokenField *)tokenField { - NSMutableString *recipient = [NSMutableString string]; - - NSMutableCharacterSet *charSet = [[[NSCharacterSet whitespaceCharacterSet] mutableCopy] autorelease]; - [charSet formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]]; - - NSString *rawStr = [[tokenField textField] text]; - for (int i = 0; i < [rawStr length]; i++) - { - if (![charSet characterIsMember:[rawStr characterAtIndex:i]]) - { - [recipient appendFormat:@"%@",[NSString stringWithFormat:@"%c", [rawStr characterAtIndex:i]]]; - } - } - - if ([rawStr length]) - { - [tokenField addTokenWithTitle:rawStr representedObject:recipient]; - } - - return NO; -} - -- (void)handleTokenFieldFrameDidChange:(NSNotification *)note -{ - if ([[note object] isEqual:_toField]) - { - [UIView animateWithDuration:0.0 - animations:^{ - [_ccField setFrame:CGRectMake(0, [_toField frame].size.height + [_toField frame].origin.y, [_ccField frame].size.width, [_ccField frame].size.height)]; - } - completion:nil]; - } -} - -@end diff --git a/JSTokenField/JSTokenField-Info.plist b/JSTokenField/JSTokenField-Info.plist deleted file mode 100644 index be21d73..0000000 --- a/JSTokenField/JSTokenField-Info.plist +++ /dev/null @@ -1,36 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleDisplayName - ${PRODUCT_NAME} - CFBundleExecutable - ${EXECUTABLE_NAME} - CFBundleIconFiles - - CFBundleIdentifier - com.jamsoftonline.${PRODUCT_NAME:rfc1034identifier} - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - ${PRODUCT_NAME} - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1.0 - LSRequiresIPhoneOS - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/JSTokenField/JSTokenField-Prefix.pch b/JSTokenField/JSTokenField-Prefix.pch deleted file mode 100644 index 4f3625e..0000000 --- a/JSTokenField/JSTokenField-Prefix.pch +++ /dev/null @@ -1,38 +0,0 @@ -// -// Copyright 2011 James Addyman (JamSoft). All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other materials -// provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY JAMES ADDYMAN (JAMSOFT) ``AS IS'' AND ANY EXPRESS OR IMPLIED -// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JAMES ADDYMAN (JAMSOFT) OR -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// The views and conclusions contained in the software and documentation are those of the -// authors and should not be interpreted as representing official policies, either expressed -// or implied, of James Addyman (JamSoft). -// - -#import - -#ifndef __IPHONE_4_0 -#warning "This project uses features only available in iOS SDK 4.0 and later." -#endif - -#ifdef __OBJC__ - #import - #import -#endif diff --git a/JSTokenField/JSTokenField.m b/JSTokenField/JSTokenField.m deleted file mode 100644 index 4bd5907..0000000 --- a/JSTokenField/JSTokenField.m +++ /dev/null @@ -1,376 +0,0 @@ -// -// Copyright 2011 James Addyman (JamSoft). All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other materials -// provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY JAMES ADDYMAN (JAMSOFT) ``AS IS'' AND ANY EXPRESS OR IMPLIED -// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JAMES ADDYMAN (JAMSOFT) OR -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// The views and conclusions contained in the software and documentation are those of the -// authors and should not be interpreted as representing official policies, either expressed -// or implied, of James Addyman (JamSoft). -// - -#import "JSTokenField.h" -#import "JSTokenButton.h" -#import - -NSString *const JSTokenFieldFrameDidChangeNotification = @"JSTokenFieldFrameDidChangeNotification"; -NSString *const JSTokenFieldNewFrameKey = @"JSTokenFieldNewFrameKey"; -NSString *const JSTokenFieldOldFrameKey = @"JSTokenFieldOldFrameKey"; -NSString *const JSDeletedTokenKey = @"JSDeletedTokenKey"; - -#define HEIGHT_PADDING 3 -#define WIDTH_PADDING 3 - -#define DEFAULT_HEIGHT 31 - -#define ZERO_WIDTH_SPACE_STRING @"\u200B" - -@interface JSTokenField (); - -- (JSTokenButton *)tokenWithString:(NSString *)string representedObject:(id)obj; -- (void)deleteHighlightedToken; - -- (void)commonSetup; -@end - - -@implementation JSTokenField - -@synthesize tokens = _tokens; -@synthesize textField = _textField; -@synthesize label = _label; -@synthesize delegate = _delegate; - -- (id)initWithFrame:(CGRect)frame -{ - if (frame.size.height < DEFAULT_HEIGHT) - { - frame.size.height = DEFAULT_HEIGHT; - } - - if ((self = [super initWithFrame:frame])) - { - [self commonSetup]; - } - - return self; -} - -- (id)initWithCoder:(NSCoder *)aDecoder { - self = [super initWithCoder:aDecoder]; - if (self) { - [self commonSetup]; - } - return self; -} - -- (void)commonSetup { - CGRect frame = self.frame; - [self setBackgroundColor:[UIColor colorWithRed:0.9 green:0.9 blue:0.9 alpha:1.0]]; - - _label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 0, frame.size.height)]; - [_label setBackgroundColor:[UIColor clearColor]]; - [_label setTextColor:[UIColor colorWithRed:0.3 green:0.3 blue:0.3 alpha:1.0]]; - [_label setFont:[UIFont fontWithName:@"Helvetica Neue" size:17.0]]; - - [self addSubview:_label]; - - // self.layer.borderColor = [[UIColor blueColor] CGColor]; - // self.layer.borderWidth = 1.0; - - _tokens = [[NSMutableArray alloc] init]; - - frame.origin.y += HEIGHT_PADDING; - frame.size.height -= HEIGHT_PADDING * 2; - _textField = [[UITextField alloc] initWithFrame:frame]; - [_textField setDelegate:self]; - [_textField setBorderStyle:UITextBorderStyleNone]; - [_textField setBackground:nil]; - [_textField setBackgroundColor:[UIColor clearColor]]; - [_textField setContentVerticalAlignment:UIControlContentVerticalAlignmentCenter]; - - // [_textField.layer setBorderColor:[[UIColor redColor] CGColor]]; - // [_textField.layer setBorderWidth:1.0]; - - [_textField setText:ZERO_WIDTH_SPACE_STRING]; - - [self addSubview:_textField]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(handleTextDidChange:) - name:UITextFieldTextDidChangeNotification - object:_textField]; -} - -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; - [_textField release], _textField = nil; - [_label release], _label = nil; - [_tokens release], _tokens = nil; - - [super dealloc]; -} - - -- (void)addTokenWithTitle:(NSString *)string representedObject:(id)obj -{ - NSString *aString = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; - - [_textField setText:ZERO_WIDTH_SPACE_STRING]; - - if ([aString length]) - { - JSTokenButton *token = [self tokenWithString:aString representedObject:obj]; - token.parentField = self; - [_tokens addObject:token]; - - if ([self.delegate respondsToSelector:@selector(tokenField:didAddToken:representedObject:)]) - { - [self.delegate tokenField:self didAddToken:aString representedObject:obj]; - } - - [self setNeedsLayout]; - } -} - -- (void)removeTokenWithTest:(BOOL (^)(JSTokenButton *token))test { - JSTokenButton *tokenToRemove = nil; - for (JSTokenButton *token in [_tokens reverseObjectEnumerator]) { - if (test(token)) { - tokenToRemove = token; - break; - } - } - - if (tokenToRemove) { - if (tokenToRemove.isFirstResponder) { - [_textField becomeFirstResponder]; - } - [tokenToRemove removeFromSuperview]; - [[tokenToRemove retain] autorelease]; // removing it from the array will dealloc the object, but we want to keep it around for the delegate method below - - [_tokens removeObject:tokenToRemove]; - if ([self.delegate respondsToSelector:@selector(tokenField:didRemoveToken:representedObject:)]) - { - NSString *tokenName = [tokenToRemove titleForState:UIControlStateNormal]; - [self.delegate tokenField:self didRemoveToken:tokenName representedObject:tokenToRemove.representedObject]; - - } - } - - [self setNeedsLayout]; -} - -- (void)removeTokenForString:(NSString *)string -{ - [self removeTokenWithTest:^BOOL(JSTokenButton *token) { - return [[token titleForState:UIControlStateNormal] isEqualToString:string]; - }]; -} - -- (void)removeTokenWithRepresentedObject:(id)representedObject { - [self removeTokenWithTest:^BOOL(JSTokenButton *token) { - return [[token representedObject] isEqual:representedObject]; - }]; -} - -- (void)deleteHighlightedToken -{ - for (int i = 0; i < [_tokens count]; i++) - { - _deletedToken = [[_tokens objectAtIndex:i] retain]; - if ([_deletedToken isToggled]) - { - [_deletedToken removeFromSuperview]; - [_tokens removeObject:_deletedToken]; - - if ([self.delegate respondsToSelector:@selector(tokenField:didRemove:representedObject:)]) - { - NSString *tokenName = [_deletedToken titleForState:UIControlStateNormal]; - [self.delegate tokenField:self didRemoveToken:tokenName representedObject:_deletedToken.representedObject]; - } - - [self setNeedsLayout]; - } - } -} - -- (JSTokenButton *)tokenWithString:(NSString *)string representedObject:(id)obj -{ - JSTokenButton *token = [JSTokenButton tokenWithString:string representedObject:obj]; - CGRect frame = [token frame]; - - if (frame.size.width > self.frame.size.width) - { - frame.size.width = self.frame.size.width - (WIDTH_PADDING * 2); - } - - [token setFrame:frame]; - - [token addTarget:self - action:@selector(toggle:) - forControlEvents:UIControlEventTouchUpInside]; - - return token; -} - -- (void)layoutSubviews -{ - CGRect currentRect = CGRectZero; - - [_label sizeToFit]; - [_label setFrame:CGRectMake(WIDTH_PADDING, HEIGHT_PADDING, [_label frame].size.width, [_label frame].size.height + 3)]; - - currentRect.origin.x += _label.frame.size.width + _label.frame.origin.x + WIDTH_PADDING; - - for (UIButton *token in _tokens) - { - CGRect frame = [token frame]; - - if ((currentRect.origin.x + frame.size.width) > self.frame.size.width) - { - currentRect.origin = CGPointMake(WIDTH_PADDING, (currentRect.origin.y + frame.size.height + HEIGHT_PADDING)); - } - - frame.origin.x = currentRect.origin.x; - frame.origin.y = currentRect.origin.y + HEIGHT_PADDING; - - [token setFrame:frame]; - - if (![token superview]) - { - [self addSubview:token]; - } - - currentRect.origin.x += frame.size.width + WIDTH_PADDING; - currentRect.size = frame.size; - } - - CGRect textFieldFrame = [_textField frame]; - - textFieldFrame.origin = currentRect.origin; - - if ((self.frame.size.width - textFieldFrame.origin.x) >= 60) - { - textFieldFrame.size.width = self.frame.size.width - textFieldFrame.origin.x; - } - else - { - textFieldFrame.size.width = self.frame.size.width; - textFieldFrame.origin = CGPointMake(WIDTH_PADDING * 2, - (currentRect.origin.y + currentRect.size.height + HEIGHT_PADDING)); - } - - textFieldFrame.origin.y += HEIGHT_PADDING; - [_textField setFrame:textFieldFrame]; - CGRect selfFrame = [self frame]; - selfFrame.size.height = textFieldFrame.origin.y + textFieldFrame.size.height + HEIGHT_PADDING; - - [UIView animateWithDuration:0.3 - animations:^{ - [self setFrame:selfFrame]; - } - completion:nil]; -} - -- (void)toggle:(id)sender -{ - for (JSTokenButton *token in _tokens) - { - [token setToggled:NO]; - } - - JSTokenButton *token = (JSTokenButton *)sender; - [token setToggled:YES]; - [token becomeFirstResponder]; -} - -- (void)setFrame:(CGRect)frame -{ - CGRect oldFrame = self.frame; - - [super setFrame:frame]; - - NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:[NSValue valueWithCGRect:frame] forKey:JSTokenFieldNewFrameKey]; - [userInfo setObject:[NSValue valueWithCGRect:oldFrame] forKey:JSTokenFieldOldFrameKey]; - if (_deletedToken) - { - [userInfo setObject:_deletedToken forKey:JSDeletedTokenKey]; - [_deletedToken release], _deletedToken = nil; - } - - if (CGRectEqualToRect(oldFrame, frame) == NO) { - [[NSNotificationCenter defaultCenter] postNotificationName:JSTokenFieldFrameDidChangeNotification object:self userInfo:[[userInfo copy] autorelease]]; - } -} - -#pragma mark - -#pragma mark UITextFieldDelegate - -- (void)handleTextDidChange:(NSNotification *)note -{ - // ensure there's always a space at the beginning - NSMutableString *text = [[[_textField text] mutableCopy] autorelease]; - if (![text hasPrefix:ZERO_WIDTH_SPACE_STRING]) - { - [text insertString:ZERO_WIDTH_SPACE_STRING atIndex:0]; - [_textField setText:text]; - } -} - -- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string -{ - if ([string isEqualToString:@""] && - (NSEqualRanges(range, NSMakeRange(0, 0)) || [[[textField text] substringWithRange:range] isEqualToString:ZERO_WIDTH_SPACE_STRING])) - { - JSTokenButton *token = [_tokens lastObject]; - [token becomeFirstResponder]; - return NO; - } - - return YES; -} - -- (BOOL)textFieldShouldReturn:(UITextField *)textField -{ - if (_textField == textField) { - if ([self.delegate respondsToSelector:@selector(tokenFieldShouldReturn:)]) { - return [self.delegate tokenFieldShouldReturn:self]; - } - } - - return NO; -} - -- (void)textFieldDidEndEditing:(UITextField *)textField -{ - if ([self.delegate respondsToSelector:@selector(tokenFieldDidEndEditing:)]) { - [self.delegate tokenFieldDidEndEditing:self]; - return; - } - else if ([[textField text] length] > 1) - { - [self addTokenWithTitle:[textField text] representedObject:[textField text]]; - [textField setText:ZERO_WIDTH_SPACE_STRING]; - } -} - -@end diff --git a/JSTokenField/en.lproj/DemoViewController.xib b/JSTokenField/en.lproj/DemoViewController.xib deleted file mode 100644 index 56f4b88..0000000 --- a/JSTokenField/en.lproj/DemoViewController.xib +++ /dev/null @@ -1,142 +0,0 @@ - - - - 1056 - 11B26 - 1617 - 1138 - 566.00 - - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - 534 - - - YES - IBProxyObject - IBUIView - - - YES - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - - - YES - - YES - - - - - YES - - IBFilesOwner - IBCocoaTouchFramework - - - IBFirstResponder - IBCocoaTouchFramework - - - - 292 - {320, 460} - - _NS:180 - - 3 - MQA - - 2 - - - IBCocoaTouchFramework - - - - - YES - - - view - - - - 13 - - - - - YES - - 0 - - - - - - -1 - - - File's Owner - - - -2 - - - - - 12 - - - YES - - - View - - - - - YES - - YES - -1.CustomClassName - -1.IBPluginDependency - -2.CustomClassName - -2.IBPluginDependency - 12.IBPluginDependency - - - YES - DemoViewController - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - UIResponder - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - - - - YES - - - - - - YES - - - - - 14 - - - 0 - IBCocoaTouchFramework - - com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3 - - - YES - 3 - 534 - - diff --git a/JSTokenField/en.lproj/InfoPlist.strings b/JSTokenField/en.lproj/InfoPlist.strings deleted file mode 100644 index 477b28f..0000000 --- a/JSTokenField/en.lproj/InfoPlist.strings +++ /dev/null @@ -1,2 +0,0 @@ -/* Localized versions of Info.plist keys */ - diff --git a/JSTokenField/main.m b/JSTokenField/main.m deleted file mode 100644 index b3255f6..0000000 --- a/JSTokenField/main.m +++ /dev/null @@ -1,42 +0,0 @@ -// -// Copyright 2011 James Addyman (JamSoft). All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other materials -// provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY JAMES ADDYMAN (JAMSOFT) ``AS IS'' AND ANY EXPRESS OR IMPLIED -// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JAMES ADDYMAN (JAMSOFT) OR -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// The views and conclusions contained in the software and documentation are those of the -// authors and should not be interpreted as representing official policies, either expressed -// or implied, of James Addyman (JamSoft). -// - -#import - -#import "DemoAppDelegate.h" - -int main(int argc, char *argv[]) -{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - - int retVal = UIApplicationMain(argc, argv, nil, NSStringFromClass([DemoAppDelegate class])); - - [pool release]; - - return retVal; -} diff --git a/JSTokenField/tokenHighlighted.png b/JSTokenField/tokenHighlighted.png deleted file mode 100644 index 631aa62..0000000 Binary files a/JSTokenField/tokenHighlighted.png and /dev/null differ diff --git a/JSTokenField/tokenHighlighted@2x.png b/JSTokenField/tokenHighlighted@2x.png deleted file mode 100644 index 1a6c3ee..0000000 Binary files a/JSTokenField/tokenHighlighted@2x.png and /dev/null differ diff --git a/JSTokenField/tokenNormal.png b/JSTokenField/tokenNormal.png deleted file mode 100644 index 8dc1930..0000000 Binary files a/JSTokenField/tokenNormal.png and /dev/null differ diff --git a/JSTokenField/tokenNormal@2x.png b/JSTokenField/tokenNormal@2x.png deleted file mode 100644 index a825484..0000000 Binary files a/JSTokenField/tokenNormal@2x.png and /dev/null differ diff --git a/tokenHighlighted.png b/tokenHighlighted.png new file mode 100644 index 0000000..128b8d7 Binary files /dev/null and b/tokenHighlighted.png differ diff --git a/tokenHighlighted@2x.png b/tokenHighlighted@2x.png new file mode 100644 index 0000000..c2870a5 Binary files /dev/null and b/tokenHighlighted@2x.png differ diff --git a/tokenNormal.png b/tokenNormal.png new file mode 100644 index 0000000..e9706cb Binary files /dev/null and b/tokenNormal.png differ diff --git a/tokenNormal@2x.png b/tokenNormal@2x.png new file mode 100644 index 0000000..f7a7141 Binary files /dev/null and b/tokenNormal@2x.png differ