├── .gitignore ├── AVTagTextView.podspec ├── CHANGELOG.md ├── Classes ├── AVHashTagsTableViewController.h ├── AVHashTagsTableViewController.m ├── AVTagTableViewControllerProtocol.h ├── NSString+AVTagAdditions.h ├── NSString+AVTagAdditions.m ├── UITextView+AVTagTextView.h └── UITextView+AVTagTextView.m ├── LICENSE ├── Project ├── AVTagTextView.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata ├── AVTagTextView.xcworkspace │ └── contents.xcworkspacedata ├── AVTagTextView │ ├── AVAppDelegate.h │ ├── AVAppDelegate.m │ ├── AVCustomTagTableViewController.h │ ├── AVCustomTagTableViewController.m │ ├── AVCustomTagTableViewController.xib │ ├── AVTagTextView-Info.plist │ ├── AVTagTextView-Prefix.pch │ ├── AVViewController.h │ ├── AVViewController.m │ ├── Base.lproj │ │ └── Main.storyboard │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── LaunchImage.launchimage │ │ │ └── Contents.json │ ├── en.lproj │ │ └── InfoPlist.strings │ └── main.m ├── AVTagTextViewTests │ ├── AVTagTextViewTests-Info.plist │ └── en.lproj │ │ └── InfoPlist.strings ├── Podfile └── Podfile.lock ├── README.md ├── Rakefile └── Tests └── AVTagTextViewSpec.m /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | */build/* 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | profile 14 | *.moved-aside 15 | DerivedData 16 | .idea/ 17 | *.hmap 18 | 19 | #CocoaPods 20 | Pods 21 | -------------------------------------------------------------------------------- /AVTagTextView.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "AVTagTextView" 3 | s.version = "0.1.0" 4 | s.summary = "A category that adds an instragram-like hashtag choosing/listing capability to the UITextView" 5 | s.homepage = "https://github.com/arsonic/AVTagTextView" 6 | s.screenshots = "https://dl.dropboxusercontent.com/u/31058381/OpenSource/AVTagTextView/screenshot.png" 7 | s.license = 'MIT' 8 | s.author = { "Arsonic" => "vershinin.arseniy@gmail.com" } 9 | s.source = { :git => "https://github.com/arsonic/AVTagTextView.git", :tag => s.version.to_s } 10 | 11 | s.platform = :ios, '6.1' 12 | s.requires_arc = true 13 | 14 | s.source_files = 'Classes' 15 | 16 | s.dependency 'ReactiveCocoa' 17 | end -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # AVTagTextView CHANGELOG 2 | 3 | ## 0.1.0 4 | 5 | Initial release. 6 | -------------------------------------------------------------------------------- /Classes/AVHashTagsTableViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AVHashTagsTableViewController.h 3 | // AVTagTextView 4 | // 5 | // Created by Arseniy Vershinin on 9/14/13. 6 | // 7 | 8 | #import 9 | 10 | #import "AVTagTableViewControllerProtocol.h" 11 | 12 | @interface AVHashTagsTableViewController : UITableViewController 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Classes/AVHashTagsTableViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // AVHashTagsTableViewController.m 3 | // AVTagTextView 4 | // 5 | // Created by Arseniy Vershinin on 9/14/13. 6 | // 7 | 8 | #import "AVHashTagsTableViewController.h" 9 | 10 | @interface AVHashTagsTableViewController () 11 | 12 | @end 13 | 14 | @implementation AVHashTagsTableViewController 15 | @synthesize hashTagsDelegate = _hashTagsDelegate; 16 | @synthesize hashTagsToDisplay = _hashTagsToDisplay; 17 | 18 | /*****************************************************/ 19 | #pragma mark - UIView Lifecycle 20 | /*****************************************************/ 21 | 22 | - (void)viewDidLoad{ 23 | [super viewDidLoad]; 24 | 25 | self.tableView.bounces = NO; 26 | } 27 | 28 | /*****************************************************/ 29 | #pragma mark - AVTagTableViewControllerProtocol 30 | /*****************************************************/ 31 | 32 | - (void)setHashTagsToDisplay:(NSArray *)hashTagsToDisplay{ 33 | if(_hashTagsToDisplay != hashTagsToDisplay) { 34 | _hashTagsToDisplay = hashTagsToDisplay; 35 | [self.tableView reloadData]; 36 | } 37 | } 38 | 39 | #pragma mark - Table view data source 40 | 41 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 42 | { 43 | return 1; 44 | } 45 | 46 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 47 | { 48 | return self.hashTagsToDisplay.count; 49 | } 50 | 51 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 52 | { 53 | static NSString *CellIdentifier = @"Cell"; 54 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 55 | 56 | if(!cell) { 57 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; 58 | } 59 | 60 | NSString *tag = self.hashTagsToDisplay[indexPath.row]; 61 | cell.textLabel.text = [NSString stringWithFormat:@"#%@", tag]; 62 | cell.selectionStyle = UITableViewCellSelectionStyleNone; 63 | 64 | return cell; 65 | } 66 | 67 | #pragma mark - Table view delegate 68 | 69 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 70 | { 71 | if(self.hashTagsDelegate && [self.hashTagsDelegate respondsToSelector:@selector(hashTagSelected:)]){ 72 | [self.hashTagsDelegate hashTagSelected:self.hashTagsToDisplay[indexPath.row]]; 73 | } 74 | } 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /Classes/AVTagTableViewControllerProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // AVTagTableViewControllerProtocol.h 3 | // AVTagTextField 4 | // 5 | // Created by Arseniy Vershinin on 10/7/13. 6 | // 7 | 8 | #import 9 | 10 | @protocol AVTagTableViewControllerDelegate 11 | /** 12 | * Should be called when a tag cell is selected 13 | */ 14 | - (void)hashTagSelected:(NSString *)tag; 15 | @end 16 | 17 | @protocol AVTagTableViewControllerProtocol 18 | 19 | /** 20 | * A list of hashtags (without the leading "#" symbol) 21 | * that should be displayed by the table view controller, 22 | * implementing the AVTagTableViewControllerProtocol. 23 | * The default implementation is the following: 24 | * 25 | *- (void)setHashTagsToDisplay:(NSArray *)hashTagsToDisplay{ 26 | if(_hashTagsToDisplay != hashTagsToDisplay) { 27 | _hashTagsToDisplay = hashTagsToDisplay; 28 | //Reload the table view as soon as the data source 29 | //has been updated: 30 | [self.tableView reloadData]; 31 | } 32 | } 33 | */ 34 | @property (nonatomic, strong) NSArray *hashTagsToDisplay; 35 | 36 | /** 37 | * The delegate, which responds to the table view hash 38 | * tags related changes (f.e. hash tags selection). 39 | * This is normally a UITextView+AVTagTextView instance 40 | */ 41 | @property (nonatomic, weak) id hashTagsDelegate; 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /Classes/NSString+AVTagAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+AVTagAdditions.h 3 | // AVTagTextField 4 | // 5 | // Created by Arseniy Vershinin on 9/16/13. 6 | // 7 | 8 | #import 9 | 10 | @interface NSString(AVTagAdditions) 11 | 12 | + (NSRegularExpression *)endOfStringHashtagRegex; 13 | 14 | - (NSRange)wholeStringRange; 15 | - (NSString *)stringWithCheckingResult:(NSTextCheckingResult *)result; 16 | - (NSString *)endOfStringHashtag; 17 | - (NSArray *)hashTags; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Classes/NSString+AVTagAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+AVTagAdditions.m 3 | // AVTagTextField 4 | // 5 | // Created by Arseniy Vershinin on 9/16/13. 6 | // 7 | 8 | #import "NSString+AVTagAdditions.h" 9 | 10 | @implementation NSString (AVTagAdditions) 11 | 12 | - (NSRange)wholeStringRange{ 13 | return NSMakeRange(0, self.length); 14 | } 15 | 16 | - (NSString *)stringWithCheckingResult:(NSTextCheckingResult *)result{ 17 | return [self substringWithRange:NSMakeRange(result.range.location+1, result.range.length-1)]; 18 | } 19 | 20 | + (NSRegularExpression *)endOfStringHashtagRegex{ 21 | return [NSRegularExpression 22 | regularExpressionWithPattern:@"#(\\w+)$" 23 | options:NSRegularExpressionCaseInsensitive 24 | error:nil]; 25 | } 26 | 27 | - (NSString *)endOfStringHashtag{ 28 | NSRegularExpression *regex = [NSString endOfStringHashtagRegex]; 29 | NSTextCheckingResult *result = [regex firstMatchInString:self options:0 range:NSMakeRange(0, self.length)]; 30 | if(result) { 31 | NSString *match = [self substringWithRange:NSMakeRange(result.range.location+1, result.range.length-1)]; 32 | return match; 33 | } 34 | return nil; 35 | } 36 | 37 | - (NSArray *)hashTags{ 38 | NSMutableArray *results = [NSMutableArray array]; 39 | if(self) { 40 | //TODO: exclude # with a regex 41 | NSRegularExpression *regex = [NSRegularExpression 42 | regularExpressionWithPattern:@"#(\\w+)" 43 | options:NSRegularExpressionCaseInsensitive 44 | error:nil]; 45 | 46 | [regex enumerateMatchesInString:self 47 | options:0 48 | range:NSMakeRange(0, [self length]) 49 | usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { 50 | 51 | NSString *match = [self substringWithRange:NSMakeRange(result.range.location+1, result.range.length-1)]; 52 | if(match) { 53 | [results addObject:match]; 54 | } 55 | }]; 56 | 57 | } 58 | return results; 59 | } 60 | 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /Classes/UITextView+AVTagTextView.h: -------------------------------------------------------------------------------- 1 | // 2 | // UITextField+AVTagTextView.h 3 | // AVTagTextView 4 | // 5 | // Created by Arseniy Vershinin on 9/13/13. 6 | // 7 | 8 | #import 9 | 10 | #import "AVTagTableViewControllerProtocol.h" 11 | 12 | #define DEFAULT_TABLE_VIEW_HEIGHT 100. 13 | 14 | /*****************************************************/ 15 | // AVTagTextViewDelegate 16 | /*****************************************************/ 17 | 18 | @protocol AVTagTextViewDelegate 19 | @optional 20 | /** 21 | * The receiver should return an array of matching string 22 | * tags without the leading # symbol. If the array is not 23 | * empty, the list of provided tags will be displayed in 24 | * the hashTagsTableViewController 25 | */ 26 | - (NSArray *)tagsForQuery:(NSString *)query; 27 | @end 28 | 29 | /*****************************************************/ 30 | // UITextView+AVTagTextView 31 | /*****************************************************/ 32 | 33 | @interface UITextView (AVTagTextView) 34 | @property (nonatomic, weak) id hashTagsDelegate; 35 | 36 | /** 37 | * Table view controller instance that will be displayed 38 | * when the tags are provided for the current user input. 39 | * It defaults to a generic table view controller 40 | * (AVHashTagsTableViewController instance), when no other 41 | * implementation is provided 42 | */ 43 | @property (nonatomic, strong) UITableViewController *hashTagsTableViewController; 44 | 45 | /** 46 | * The height of the displayed hash tags table view controller, 47 | * measured from the bottom of the iDevice keyboard. Defaults 48 | * to DEFAULT_TABLE_VIEW_HEIGHT 49 | */ 50 | @property (nonatomic, assign) CGFloat hashTagsTableViewHeight; 51 | 52 | /** 53 | * Hash tags, encountered in the current TextView's text. 54 | * The hash tags do not include the "#" symbol 55 | */ 56 | @property (nonatomic, readonly) NSArray *hashTags; 57 | 58 | @end 59 | 60 | @interface UITextView (AVTagTextViewTesting) 61 | - (void)simulteUserInput:(NSString *)input; 62 | @end -------------------------------------------------------------------------------- /Classes/UITextView+AVTagTextView.m: -------------------------------------------------------------------------------- 1 | // 2 | // UITextField+AVTagTextView.m 3 | // AVTagTextView 4 | // 5 | // Created by Arseniy Vershinin on 9/13/13. 6 | // 7 | 8 | #import "UITextView+AVTagTextView.h" 9 | 10 | #import "AVHashTagsTableViewController.h" 11 | 12 | #import 13 | 14 | #import "NSString+AVTagAdditions.h" 15 | 16 | #import 17 | #import 18 | 19 | @implementation UITextView (AVTagTextView) 20 | 21 | /*****************************************************/ 22 | #pragma mark - Properties 23 | /*****************************************************/ 24 | 25 | static const char *kHashTagsDelegateKey = "hashTagsDelegate"; 26 | 27 | - (void)setHashTagsDelegate:(id)hashTagsDelegate{ 28 | if(self.hashTagsDelegate != hashTagsDelegate) { 29 | //Adding the hash delegate initializes user input observation process: 30 | [self addSignals]; 31 | objc_setAssociatedObject(self, kHashTagsDelegateKey, hashTagsDelegate, OBJC_ASSOCIATION_ASSIGN); 32 | } 33 | } 34 | 35 | - (id)hashTagsDelegate{ 36 | return objc_getAssociatedObject(self, kHashTagsDelegateKey); 37 | } 38 | 39 | static const char *kHashTagsTableViewControllerKey = "hashTagsTableViewControllerKey"; 40 | 41 | - (void)setHashTagsTableViewController:(UITableViewController *)hashTagsTableViewController{ 42 | if(self.hashTagsTableViewController != hashTagsTableViewController) { 43 | 44 | hashTagsTableViewController.hashTagsDelegate = self; 45 | objc_setAssociatedObject(self, kHashTagsTableViewControllerKey, hashTagsTableViewController, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 46 | } 47 | } 48 | 49 | - (UITableViewController *)hashTagsTableViewController{ 50 | //lazily create a table view controller if there isn't one 51 | UITableViewController *controller = objc_getAssociatedObject(self, kHashTagsTableViewControllerKey); 52 | if(!controller) { 53 | controller = [AVHashTagsTableViewController new]; 54 | controller.hashTagsDelegate = self; 55 | objc_setAssociatedObject(self, kHashTagsTableViewControllerKey, controller, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 56 | } 57 | return controller; 58 | } 59 | 60 | static const char *kHashTagsTableViewHeightKey = "hashTagsTableViewHeightKey"; 61 | 62 | - (void)setHashTagsTableViewHeight:(CGFloat)hashTagsTableViewHeight{ 63 | objc_setAssociatedObject(self, kHashTagsTableViewHeightKey, @(hashTagsTableViewHeight), OBJC_ASSOCIATION_RETAIN_NONATOMIC); 64 | } 65 | 66 | - (CGFloat)hashTagsTableViewHeight{ 67 | CGFloat height = [objc_getAssociatedObject(self, kHashTagsTableViewHeightKey) floatValue]; 68 | if(height <= 0) { 69 | height = DEFAULT_TABLE_VIEW_HEIGHT; 70 | } 71 | 72 | return height; 73 | } 74 | 75 | - (NSArray *)hashTags{ 76 | return [self.text hashTags]; 77 | } 78 | 79 | /*****************************************************/ 80 | #pragma mark - Helpers 81 | /*****************************************************/ 82 | 83 | - (void)addTextSignal{ 84 | [[self rac_textSignal] subscribeNext:^(NSString *input) { 85 | [self reactOnUserInput:input]; 86 | }]; 87 | } 88 | 89 | - (void)addSignals{ 90 | [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillHideNotification object:nil] subscribeNext:^(id x) { 91 | 92 | self.hashTagsTableViewController.view.hidden = YES; 93 | }]; 94 | 95 | [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardDidShowNotification object:nil] subscribeNext:^(NSNotification *notification) { 96 | 97 | CGRect keyboardRect = [[notification.userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue]; 98 | CGRect tableViewFrame = CGRectMake(0, 99 | keyboardRect.origin.y - keyboardRect.size.height - self.hashTagsTableViewHeight, 100 | keyboardRect.size.width, 101 | self.hashTagsTableViewHeight); 102 | 103 | self.hashTagsTableViewController.view.frame = tableViewFrame; 104 | }]; 105 | 106 | //The following code allows the user to reassign the delegate 107 | //without hindering reactive cocoa's input observation. 108 | //startWith guarantees that the block will be ran the first 109 | //time without wating for the delegate property to change 110 | [[RACObserve(self, delegate) startWith:nil] subscribeNext:^(id x) { 111 | [self addTextSignal]; 112 | }]; 113 | } 114 | 115 | - (void)updateHashTagsTableViewControllerWithTags:(NSArray *)tags{ 116 | UITableViewController *controller = self.hashTagsTableViewController; 117 | 118 | //Add the controller to the current root view controller if it hasn't been added anywhere so far 119 | if(!controller.tableView.superview){ 120 | UIViewController *rootViewController = [UIApplication sharedApplication].keyWindow.rootViewController; 121 | 122 | [controller willMoveToParentViewController:rootViewController]; 123 | [rootViewController.view addSubview:controller.view]; 124 | [controller didMoveToParentViewController:rootViewController]; 125 | } 126 | controller.view.hidden = tags.count == 0; 127 | controller.hashTagsToDisplay = tags; 128 | } 129 | 130 | #pragma mark AVTagTableViewDelegate 131 | 132 | - (void)hashTagSelected:(NSString *)tag{ 133 | NSString *replaceString = [NSString stringWithFormat:@"#%@ ", tag]; 134 | NSMutableString *mutableText = [NSMutableString stringWithString:self.text]; 135 | 136 | [[NSString endOfStringHashtagRegex] replaceMatchesInString:mutableText options:0 range:[self.text wholeStringRange] withTemplate:replaceString]; 137 | self.text = mutableText; 138 | 139 | self.hashTagsTableViewController.view.hidden = YES; 140 | } 141 | 142 | /*****************************************************/ 143 | #pragma mark - Interface 144 | /*****************************************************/ 145 | 146 | - (void)reactOnUserInput:(NSString *)input{ 147 | NSString *endHashTag = [input endOfStringHashtag]; 148 | if(endHashTag.length > 0 && 149 | input.length > 0 && 150 | [self.hashTagsDelegate respondsToSelector:@selector(tagsForQuery:)]) { 151 | 152 | NSArray *hashTags = [self.hashTagsDelegate tagsForQuery:endHashTag]; 153 | [self updateHashTagsTableViewControllerWithTags:hashTags]; 154 | } 155 | else{ 156 | self.hashTagsTableViewController.view.hidden = YES; 157 | } 158 | } 159 | 160 | /*****************************************************/ 161 | #pragma mark - Test Methds 162 | /*****************************************************/ 163 | 164 | - (void)simulteUserInput:(NSString *)input{ 165 | self.text = self.text?:@""; 166 | self.text = [self.text stringByAppendingString:input]; 167 | [self reactOnUserInput:self.text]; 168 | } 169 | 170 | @end -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Arsonic 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Project/AVTagTextView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 2D4D931E1811DEBF00903297 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D4D931D1811DEBF00903297 /* Foundation.framework */; }; 11 | 2D4D93201811DEBF00903297 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D4D931F1811DEBF00903297 /* CoreGraphics.framework */; }; 12 | 2D4D93221811DEBF00903297 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D4D93211811DEBF00903297 /* UIKit.framework */; }; 13 | 2D4D93281811DEBF00903297 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 2D4D93261811DEBF00903297 /* InfoPlist.strings */; }; 14 | 2D4D932A1811DEBF00903297 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D4D93291811DEBF00903297 /* main.m */; }; 15 | 2D4D932E1811DEBF00903297 /* AVAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D4D932D1811DEBF00903297 /* AVAppDelegate.m */; }; 16 | 2D4D93311811DEBF00903297 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2D4D932F1811DEBF00903297 /* Main.storyboard */; }; 17 | 2D4D93341811DEBF00903297 /* AVViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D4D93331811DEBF00903297 /* AVViewController.m */; }; 18 | 2D4D93361811DEBF00903297 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2D4D93351811DEBF00903297 /* Images.xcassets */; }; 19 | 2D4D933D1811DEBF00903297 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D4D933C1811DEBF00903297 /* XCTest.framework */; }; 20 | 2D4D933E1811DEBF00903297 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D4D931D1811DEBF00903297 /* Foundation.framework */; }; 21 | 2D4D933F1811DEBF00903297 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D4D93211811DEBF00903297 /* UIKit.framework */; }; 22 | 2D4D93471811DEBF00903297 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 2D4D93451811DEBF00903297 /* InfoPlist.strings */; }; 23 | 2DF201041811E31B00BAB170 /* AVCustomTagTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DF201021811E31B00BAB170 /* AVCustomTagTableViewController.m */; }; 24 | 2DF201051811E31B00BAB170 /* AVCustomTagTableViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2DF201031811E31B00BAB170 /* AVCustomTagTableViewController.xib */; }; 25 | 2DF201071811E35500BAB170 /* AVTagTextViewSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DF201061811E35500BAB170 /* AVTagTextViewSpec.m */; }; 26 | 4FF60A7CF38F49F1A53F12D7 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8734AF505AF74CD9BF980484 /* libPods.a */; }; 27 | 68CAA5FE50C74F6390AB2B00 /* libPods-AVTagTextViewTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DACF7587EEA47368BAFD0A5 /* libPods-AVTagTextViewTests.a */; }; 28 | /* End PBXBuildFile section */ 29 | 30 | /* Begin PBXContainerItemProxy section */ 31 | 2D4D93401811DEBF00903297 /* PBXContainerItemProxy */ = { 32 | isa = PBXContainerItemProxy; 33 | containerPortal = 2D4D93121811DEBF00903297 /* Project object */; 34 | proxyType = 1; 35 | remoteGlobalIDString = 2D4D93191811DEBF00903297; 36 | remoteInfo = AVTagTextView; 37 | }; 38 | /* End PBXContainerItemProxy section */ 39 | 40 | /* Begin PBXFileReference section */ 41 | 2D4D931A1811DEBF00903297 /* AVTagTextView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AVTagTextView.app; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | 2D4D931D1811DEBF00903297 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 43 | 2D4D931F1811DEBF00903297 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 44 | 2D4D93211811DEBF00903297 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 45 | 2D4D93251811DEBF00903297 /* AVTagTextView-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "AVTagTextView-Info.plist"; sourceTree = ""; }; 46 | 2D4D93271811DEBF00903297 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 47 | 2D4D93291811DEBF00903297 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 48 | 2D4D932B1811DEBF00903297 /* AVTagTextView-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AVTagTextView-Prefix.pch"; sourceTree = ""; }; 49 | 2D4D932C1811DEBF00903297 /* AVAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AVAppDelegate.h; sourceTree = ""; }; 50 | 2D4D932D1811DEBF00903297 /* AVAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AVAppDelegate.m; sourceTree = ""; }; 51 | 2D4D93301811DEBF00903297 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 52 | 2D4D93321811DEBF00903297 /* AVViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AVViewController.h; sourceTree = ""; }; 53 | 2D4D93331811DEBF00903297 /* AVViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AVViewController.m; sourceTree = ""; }; 54 | 2D4D93351811DEBF00903297 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 55 | 2D4D933B1811DEBF00903297 /* AVTagTextViewTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AVTagTextViewTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 56 | 2D4D933C1811DEBF00903297 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 57 | 2D4D93441811DEBF00903297 /* AVTagTextViewTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "AVTagTextViewTests-Info.plist"; sourceTree = ""; }; 58 | 2D4D93461811DEBF00903297 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 59 | 2DF201011811E31B00BAB170 /* AVCustomTagTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AVCustomTagTableViewController.h; sourceTree = ""; }; 60 | 2DF201021811E31B00BAB170 /* AVCustomTagTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AVCustomTagTableViewController.m; sourceTree = ""; }; 61 | 2DF201031811E31B00BAB170 /* AVCustomTagTableViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AVCustomTagTableViewController.xib; sourceTree = ""; }; 62 | 2DF201061811E35500BAB170 /* AVTagTextViewSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AVTagTextViewSpec.m; path = ../../Tests/AVTagTextViewSpec.m; sourceTree = ""; }; 63 | 3CAF57F86D3B49F3BF0A0471 /* Pods.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.xcconfig; path = Pods/Pods.xcconfig; sourceTree = ""; }; 64 | 8734AF505AF74CD9BF980484 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; 65 | 9DACF7587EEA47368BAFD0A5 /* libPods-AVTagTextViewTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AVTagTextViewTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 66 | B5668A2D11034B13A0BE0714 /* Pods-AVTagTextViewTests.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AVTagTextViewTests.xcconfig"; path = "Pods/Pods-AVTagTextViewTests.xcconfig"; sourceTree = ""; }; 67 | /* End PBXFileReference section */ 68 | 69 | /* Begin PBXFrameworksBuildPhase section */ 70 | 2D4D93171811DEBF00903297 /* Frameworks */ = { 71 | isa = PBXFrameworksBuildPhase; 72 | buildActionMask = 2147483647; 73 | files = ( 74 | 2D4D93201811DEBF00903297 /* CoreGraphics.framework in Frameworks */, 75 | 2D4D93221811DEBF00903297 /* UIKit.framework in Frameworks */, 76 | 2D4D931E1811DEBF00903297 /* Foundation.framework in Frameworks */, 77 | 4FF60A7CF38F49F1A53F12D7 /* libPods.a in Frameworks */, 78 | ); 79 | runOnlyForDeploymentPostprocessing = 0; 80 | }; 81 | 2D4D93381811DEBF00903297 /* Frameworks */ = { 82 | isa = PBXFrameworksBuildPhase; 83 | buildActionMask = 2147483647; 84 | files = ( 85 | 2D4D933D1811DEBF00903297 /* XCTest.framework in Frameworks */, 86 | 2D4D933F1811DEBF00903297 /* UIKit.framework in Frameworks */, 87 | 2D4D933E1811DEBF00903297 /* Foundation.framework in Frameworks */, 88 | 68CAA5FE50C74F6390AB2B00 /* libPods-AVTagTextViewTests.a in Frameworks */, 89 | ); 90 | runOnlyForDeploymentPostprocessing = 0; 91 | }; 92 | /* End PBXFrameworksBuildPhase section */ 93 | 94 | /* Begin PBXGroup section */ 95 | 2D4D93111811DEBF00903297 = { 96 | isa = PBXGroup; 97 | children = ( 98 | 2D4D93231811DEBF00903297 /* AVTagTextView */, 99 | 2D4D93421811DEBF00903297 /* AVTagTextViewTests */, 100 | 2D4D931C1811DEBF00903297 /* Frameworks */, 101 | 2D4D931B1811DEBF00903297 /* Products */, 102 | 3CAF57F86D3B49F3BF0A0471 /* Pods.xcconfig */, 103 | B5668A2D11034B13A0BE0714 /* Pods-AVTagTextViewTests.xcconfig */, 104 | ); 105 | sourceTree = ""; 106 | }; 107 | 2D4D931B1811DEBF00903297 /* Products */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | 2D4D931A1811DEBF00903297 /* AVTagTextView.app */, 111 | 2D4D933B1811DEBF00903297 /* AVTagTextViewTests.xctest */, 112 | ); 113 | name = Products; 114 | sourceTree = ""; 115 | }; 116 | 2D4D931C1811DEBF00903297 /* Frameworks */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | 2D4D931D1811DEBF00903297 /* Foundation.framework */, 120 | 2D4D931F1811DEBF00903297 /* CoreGraphics.framework */, 121 | 2D4D93211811DEBF00903297 /* UIKit.framework */, 122 | 2D4D933C1811DEBF00903297 /* XCTest.framework */, 123 | 8734AF505AF74CD9BF980484 /* libPods.a */, 124 | 9DACF7587EEA47368BAFD0A5 /* libPods-AVTagTextViewTests.a */, 125 | ); 126 | name = Frameworks; 127 | sourceTree = ""; 128 | }; 129 | 2D4D93231811DEBF00903297 /* AVTagTextView */ = { 130 | isa = PBXGroup; 131 | children = ( 132 | 2DF201011811E31B00BAB170 /* AVCustomTagTableViewController.h */, 133 | 2DF201021811E31B00BAB170 /* AVCustomTagTableViewController.m */, 134 | 2DF201031811E31B00BAB170 /* AVCustomTagTableViewController.xib */, 135 | 2D4D932C1811DEBF00903297 /* AVAppDelegate.h */, 136 | 2D4D932D1811DEBF00903297 /* AVAppDelegate.m */, 137 | 2D4D932F1811DEBF00903297 /* Main.storyboard */, 138 | 2D4D93321811DEBF00903297 /* AVViewController.h */, 139 | 2D4D93331811DEBF00903297 /* AVViewController.m */, 140 | 2D4D93351811DEBF00903297 /* Images.xcassets */, 141 | 2D4D93241811DEBF00903297 /* Supporting Files */, 142 | ); 143 | path = AVTagTextView; 144 | sourceTree = ""; 145 | }; 146 | 2D4D93241811DEBF00903297 /* Supporting Files */ = { 147 | isa = PBXGroup; 148 | children = ( 149 | 2D4D93251811DEBF00903297 /* AVTagTextView-Info.plist */, 150 | 2D4D93261811DEBF00903297 /* InfoPlist.strings */, 151 | 2D4D93291811DEBF00903297 /* main.m */, 152 | 2D4D932B1811DEBF00903297 /* AVTagTextView-Prefix.pch */, 153 | ); 154 | name = "Supporting Files"; 155 | sourceTree = ""; 156 | }; 157 | 2D4D93421811DEBF00903297 /* AVTagTextViewTests */ = { 158 | isa = PBXGroup; 159 | children = ( 160 | 2DF201061811E35500BAB170 /* AVTagTextViewSpec.m */, 161 | 2D4D93431811DEBF00903297 /* Supporting Files */, 162 | ); 163 | path = AVTagTextViewTests; 164 | sourceTree = ""; 165 | }; 166 | 2D4D93431811DEBF00903297 /* Supporting Files */ = { 167 | isa = PBXGroup; 168 | children = ( 169 | 2D4D93441811DEBF00903297 /* AVTagTextViewTests-Info.plist */, 170 | 2D4D93451811DEBF00903297 /* InfoPlist.strings */, 171 | ); 172 | name = "Supporting Files"; 173 | sourceTree = ""; 174 | }; 175 | /* End PBXGroup section */ 176 | 177 | /* Begin PBXNativeTarget section */ 178 | 2D4D93191811DEBF00903297 /* AVTagTextView */ = { 179 | isa = PBXNativeTarget; 180 | buildConfigurationList = 2D4D934C1811DEBF00903297 /* Build configuration list for PBXNativeTarget "AVTagTextView" */; 181 | buildPhases = ( 182 | 18249F0CDA724D16AC28D551 /* Check Pods Manifest.lock */, 183 | 2D4D93161811DEBF00903297 /* Sources */, 184 | 2D4D93171811DEBF00903297 /* Frameworks */, 185 | 2D4D93181811DEBF00903297 /* Resources */, 186 | F0AA98EF69944AD8AF0D8FC1 /* Copy Pods Resources */, 187 | ); 188 | buildRules = ( 189 | ); 190 | dependencies = ( 191 | ); 192 | name = AVTagTextView; 193 | productName = AVTagTextView; 194 | productReference = 2D4D931A1811DEBF00903297 /* AVTagTextView.app */; 195 | productType = "com.apple.product-type.application"; 196 | }; 197 | 2D4D933A1811DEBF00903297 /* AVTagTextViewTests */ = { 198 | isa = PBXNativeTarget; 199 | buildConfigurationList = 2D4D934F1811DEBF00903297 /* Build configuration list for PBXNativeTarget "AVTagTextViewTests" */; 200 | buildPhases = ( 201 | 7A59C6213004403B80CF90E8 /* Check Pods Manifest.lock */, 202 | 2D4D93371811DEBF00903297 /* Sources */, 203 | 2D4D93381811DEBF00903297 /* Frameworks */, 204 | 2D4D93391811DEBF00903297 /* Resources */, 205 | 67024BD66FA342FA811840E8 /* Copy Pods Resources */, 206 | ); 207 | buildRules = ( 208 | ); 209 | dependencies = ( 210 | 2D4D93411811DEBF00903297 /* PBXTargetDependency */, 211 | ); 212 | name = AVTagTextViewTests; 213 | productName = AVTagTextViewTests; 214 | productReference = 2D4D933B1811DEBF00903297 /* AVTagTextViewTests.xctest */; 215 | productType = "com.apple.product-type.bundle.unit-test"; 216 | }; 217 | /* End PBXNativeTarget section */ 218 | 219 | /* Begin PBXProject section */ 220 | 2D4D93121811DEBF00903297 /* Project object */ = { 221 | isa = PBXProject; 222 | attributes = { 223 | CLASSPREFIX = AV; 224 | LastUpgradeCheck = 0500; 225 | ORGANIZATIONNAME = "Arseniy Vershinin"; 226 | TargetAttributes = { 227 | 2D4D933A1811DEBF00903297 = { 228 | TestTargetID = 2D4D93191811DEBF00903297; 229 | }; 230 | }; 231 | }; 232 | buildConfigurationList = 2D4D93151811DEBF00903297 /* Build configuration list for PBXProject "AVTagTextView" */; 233 | compatibilityVersion = "Xcode 3.2"; 234 | developmentRegion = English; 235 | hasScannedForEncodings = 0; 236 | knownRegions = ( 237 | en, 238 | Base, 239 | ); 240 | mainGroup = 2D4D93111811DEBF00903297; 241 | productRefGroup = 2D4D931B1811DEBF00903297 /* Products */; 242 | projectDirPath = ""; 243 | projectRoot = ""; 244 | targets = ( 245 | 2D4D93191811DEBF00903297 /* AVTagTextView */, 246 | 2D4D933A1811DEBF00903297 /* AVTagTextViewTests */, 247 | ); 248 | }; 249 | /* End PBXProject section */ 250 | 251 | /* Begin PBXResourcesBuildPhase section */ 252 | 2D4D93181811DEBF00903297 /* Resources */ = { 253 | isa = PBXResourcesBuildPhase; 254 | buildActionMask = 2147483647; 255 | files = ( 256 | 2DF201051811E31B00BAB170 /* AVCustomTagTableViewController.xib in Resources */, 257 | 2D4D93361811DEBF00903297 /* Images.xcassets in Resources */, 258 | 2D4D93281811DEBF00903297 /* InfoPlist.strings in Resources */, 259 | 2D4D93311811DEBF00903297 /* Main.storyboard in Resources */, 260 | ); 261 | runOnlyForDeploymentPostprocessing = 0; 262 | }; 263 | 2D4D93391811DEBF00903297 /* Resources */ = { 264 | isa = PBXResourcesBuildPhase; 265 | buildActionMask = 2147483647; 266 | files = ( 267 | 2D4D93471811DEBF00903297 /* InfoPlist.strings in Resources */, 268 | ); 269 | runOnlyForDeploymentPostprocessing = 0; 270 | }; 271 | /* End PBXResourcesBuildPhase section */ 272 | 273 | /* Begin PBXShellScriptBuildPhase section */ 274 | 18249F0CDA724D16AC28D551 /* Check Pods Manifest.lock */ = { 275 | isa = PBXShellScriptBuildPhase; 276 | buildActionMask = 2147483647; 277 | files = ( 278 | ); 279 | inputPaths = ( 280 | ); 281 | name = "Check Pods Manifest.lock"; 282 | outputPaths = ( 283 | ); 284 | runOnlyForDeploymentPostprocessing = 0; 285 | shellPath = /bin/sh; 286 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 287 | showEnvVarsInLog = 0; 288 | }; 289 | 67024BD66FA342FA811840E8 /* Copy Pods Resources */ = { 290 | isa = PBXShellScriptBuildPhase; 291 | buildActionMask = 2147483647; 292 | files = ( 293 | ); 294 | inputPaths = ( 295 | ); 296 | name = "Copy Pods Resources"; 297 | outputPaths = ( 298 | ); 299 | runOnlyForDeploymentPostprocessing = 0; 300 | shellPath = /bin/sh; 301 | shellScript = "\"${SRCROOT}/Pods/Pods-AVTagTextViewTests-resources.sh\"\n"; 302 | showEnvVarsInLog = 0; 303 | }; 304 | 7A59C6213004403B80CF90E8 /* Check Pods Manifest.lock */ = { 305 | isa = PBXShellScriptBuildPhase; 306 | buildActionMask = 2147483647; 307 | files = ( 308 | ); 309 | inputPaths = ( 310 | ); 311 | name = "Check Pods Manifest.lock"; 312 | outputPaths = ( 313 | ); 314 | runOnlyForDeploymentPostprocessing = 0; 315 | shellPath = /bin/sh; 316 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 317 | showEnvVarsInLog = 0; 318 | }; 319 | F0AA98EF69944AD8AF0D8FC1 /* Copy Pods Resources */ = { 320 | isa = PBXShellScriptBuildPhase; 321 | buildActionMask = 2147483647; 322 | files = ( 323 | ); 324 | inputPaths = ( 325 | ); 326 | name = "Copy Pods Resources"; 327 | outputPaths = ( 328 | ); 329 | runOnlyForDeploymentPostprocessing = 0; 330 | shellPath = /bin/sh; 331 | shellScript = "\"${SRCROOT}/Pods/Pods-resources.sh\"\n"; 332 | showEnvVarsInLog = 0; 333 | }; 334 | /* End PBXShellScriptBuildPhase section */ 335 | 336 | /* Begin PBXSourcesBuildPhase section */ 337 | 2D4D93161811DEBF00903297 /* Sources */ = { 338 | isa = PBXSourcesBuildPhase; 339 | buildActionMask = 2147483647; 340 | files = ( 341 | 2DF201041811E31B00BAB170 /* AVCustomTagTableViewController.m in Sources */, 342 | 2D4D932A1811DEBF00903297 /* main.m in Sources */, 343 | 2D4D93341811DEBF00903297 /* AVViewController.m in Sources */, 344 | 2D4D932E1811DEBF00903297 /* AVAppDelegate.m in Sources */, 345 | ); 346 | runOnlyForDeploymentPostprocessing = 0; 347 | }; 348 | 2D4D93371811DEBF00903297 /* Sources */ = { 349 | isa = PBXSourcesBuildPhase; 350 | buildActionMask = 2147483647; 351 | files = ( 352 | 2DF201071811E35500BAB170 /* AVTagTextViewSpec.m in Sources */, 353 | ); 354 | runOnlyForDeploymentPostprocessing = 0; 355 | }; 356 | /* End PBXSourcesBuildPhase section */ 357 | 358 | /* Begin PBXTargetDependency section */ 359 | 2D4D93411811DEBF00903297 /* PBXTargetDependency */ = { 360 | isa = PBXTargetDependency; 361 | target = 2D4D93191811DEBF00903297 /* AVTagTextView */; 362 | targetProxy = 2D4D93401811DEBF00903297 /* PBXContainerItemProxy */; 363 | }; 364 | /* End PBXTargetDependency section */ 365 | 366 | /* Begin PBXVariantGroup section */ 367 | 2D4D93261811DEBF00903297 /* InfoPlist.strings */ = { 368 | isa = PBXVariantGroup; 369 | children = ( 370 | 2D4D93271811DEBF00903297 /* en */, 371 | ); 372 | name = InfoPlist.strings; 373 | sourceTree = ""; 374 | }; 375 | 2D4D932F1811DEBF00903297 /* Main.storyboard */ = { 376 | isa = PBXVariantGroup; 377 | children = ( 378 | 2D4D93301811DEBF00903297 /* Base */, 379 | ); 380 | name = Main.storyboard; 381 | sourceTree = ""; 382 | }; 383 | 2D4D93451811DEBF00903297 /* InfoPlist.strings */ = { 384 | isa = PBXVariantGroup; 385 | children = ( 386 | 2D4D93461811DEBF00903297 /* en */, 387 | ); 388 | name = InfoPlist.strings; 389 | sourceTree = ""; 390 | }; 391 | /* End PBXVariantGroup section */ 392 | 393 | /* Begin XCBuildConfiguration section */ 394 | 2D4D934A1811DEBF00903297 /* Debug */ = { 395 | isa = XCBuildConfiguration; 396 | buildSettings = { 397 | ALWAYS_SEARCH_USER_PATHS = NO; 398 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 399 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 400 | CLANG_CXX_LIBRARY = "libc++"; 401 | CLANG_ENABLE_MODULES = YES; 402 | CLANG_ENABLE_OBJC_ARC = YES; 403 | CLANG_WARN_BOOL_CONVERSION = YES; 404 | CLANG_WARN_CONSTANT_CONVERSION = YES; 405 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 406 | CLANG_WARN_EMPTY_BODY = YES; 407 | CLANG_WARN_ENUM_CONVERSION = YES; 408 | CLANG_WARN_INT_CONVERSION = YES; 409 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 410 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 411 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 412 | COPY_PHASE_STRIP = NO; 413 | GCC_C_LANGUAGE_STANDARD = gnu99; 414 | GCC_DYNAMIC_NO_PIC = NO; 415 | GCC_OPTIMIZATION_LEVEL = 0; 416 | GCC_PREPROCESSOR_DEFINITIONS = ( 417 | "DEBUG=1", 418 | "$(inherited)", 419 | ); 420 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 421 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 422 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 423 | GCC_WARN_UNDECLARED_SELECTOR = YES; 424 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 425 | GCC_WARN_UNUSED_FUNCTION = YES; 426 | GCC_WARN_UNUSED_VARIABLE = YES; 427 | IPHONEOS_DEPLOYMENT_TARGET = 6.1; 428 | ONLY_ACTIVE_ARCH = YES; 429 | SDKROOT = iphoneos; 430 | }; 431 | name = Debug; 432 | }; 433 | 2D4D934B1811DEBF00903297 /* Release */ = { 434 | isa = XCBuildConfiguration; 435 | buildSettings = { 436 | ALWAYS_SEARCH_USER_PATHS = NO; 437 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 438 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 439 | CLANG_CXX_LIBRARY = "libc++"; 440 | CLANG_ENABLE_MODULES = YES; 441 | CLANG_ENABLE_OBJC_ARC = YES; 442 | CLANG_WARN_BOOL_CONVERSION = YES; 443 | CLANG_WARN_CONSTANT_CONVERSION = YES; 444 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 445 | CLANG_WARN_EMPTY_BODY = YES; 446 | CLANG_WARN_ENUM_CONVERSION = YES; 447 | CLANG_WARN_INT_CONVERSION = YES; 448 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 449 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 450 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 451 | COPY_PHASE_STRIP = YES; 452 | ENABLE_NS_ASSERTIONS = NO; 453 | GCC_C_LANGUAGE_STANDARD = gnu99; 454 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 455 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 456 | GCC_WARN_UNDECLARED_SELECTOR = YES; 457 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 458 | GCC_WARN_UNUSED_FUNCTION = YES; 459 | GCC_WARN_UNUSED_VARIABLE = YES; 460 | IPHONEOS_DEPLOYMENT_TARGET = 6.1; 461 | SDKROOT = iphoneos; 462 | VALIDATE_PRODUCT = YES; 463 | }; 464 | name = Release; 465 | }; 466 | 2D4D934D1811DEBF00903297 /* Debug */ = { 467 | isa = XCBuildConfiguration; 468 | baseConfigurationReference = 3CAF57F86D3B49F3BF0A0471 /* Pods.xcconfig */; 469 | buildSettings = { 470 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 471 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 472 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 473 | GCC_PREFIX_HEADER = "AVTagTextView/AVTagTextView-Prefix.pch"; 474 | INFOPLIST_FILE = "AVTagTextView/AVTagTextView-Info.plist"; 475 | PRODUCT_NAME = "$(TARGET_NAME)"; 476 | WRAPPER_EXTENSION = app; 477 | }; 478 | name = Debug; 479 | }; 480 | 2D4D934E1811DEBF00903297 /* Release */ = { 481 | isa = XCBuildConfiguration; 482 | baseConfigurationReference = 3CAF57F86D3B49F3BF0A0471 /* Pods.xcconfig */; 483 | buildSettings = { 484 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 485 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 486 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 487 | GCC_PREFIX_HEADER = "AVTagTextView/AVTagTextView-Prefix.pch"; 488 | INFOPLIST_FILE = "AVTagTextView/AVTagTextView-Info.plist"; 489 | PRODUCT_NAME = "$(TARGET_NAME)"; 490 | WRAPPER_EXTENSION = app; 491 | }; 492 | name = Release; 493 | }; 494 | 2D4D93501811DEBF00903297 /* Debug */ = { 495 | isa = XCBuildConfiguration; 496 | baseConfigurationReference = B5668A2D11034B13A0BE0714 /* Pods-AVTagTextViewTests.xcconfig */; 497 | buildSettings = { 498 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 499 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/AVTagTextView.app/AVTagTextView"; 500 | FRAMEWORK_SEARCH_PATHS = ( 501 | "$(SDKROOT)/Developer/Library/Frameworks", 502 | "$(inherited)", 503 | "$(DEVELOPER_FRAMEWORKS_DIR)", 504 | ); 505 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 506 | GCC_PREFIX_HEADER = "AVTagTextView/AVTagTextView-Prefix.pch"; 507 | GCC_PREPROCESSOR_DEFINITIONS = ( 508 | "DEBUG=1", 509 | "$(inherited)", 510 | ); 511 | INFOPLIST_FILE = "AVTagTextViewTests/AVTagTextViewTests-Info.plist"; 512 | PRODUCT_NAME = "$(TARGET_NAME)"; 513 | TEST_HOST = "$(BUNDLE_LOADER)"; 514 | WRAPPER_EXTENSION = xctest; 515 | }; 516 | name = Debug; 517 | }; 518 | 2D4D93511811DEBF00903297 /* Release */ = { 519 | isa = XCBuildConfiguration; 520 | baseConfigurationReference = B5668A2D11034B13A0BE0714 /* Pods-AVTagTextViewTests.xcconfig */; 521 | buildSettings = { 522 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 523 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/AVTagTextView.app/AVTagTextView"; 524 | FRAMEWORK_SEARCH_PATHS = ( 525 | "$(SDKROOT)/Developer/Library/Frameworks", 526 | "$(inherited)", 527 | "$(DEVELOPER_FRAMEWORKS_DIR)", 528 | ); 529 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 530 | GCC_PREFIX_HEADER = "AVTagTextView/AVTagTextView-Prefix.pch"; 531 | INFOPLIST_FILE = "AVTagTextViewTests/AVTagTextViewTests-Info.plist"; 532 | PRODUCT_NAME = "$(TARGET_NAME)"; 533 | TEST_HOST = "$(BUNDLE_LOADER)"; 534 | WRAPPER_EXTENSION = xctest; 535 | }; 536 | name = Release; 537 | }; 538 | /* End XCBuildConfiguration section */ 539 | 540 | /* Begin XCConfigurationList section */ 541 | 2D4D93151811DEBF00903297 /* Build configuration list for PBXProject "AVTagTextView" */ = { 542 | isa = XCConfigurationList; 543 | buildConfigurations = ( 544 | 2D4D934A1811DEBF00903297 /* Debug */, 545 | 2D4D934B1811DEBF00903297 /* Release */, 546 | ); 547 | defaultConfigurationIsVisible = 0; 548 | defaultConfigurationName = Release; 549 | }; 550 | 2D4D934C1811DEBF00903297 /* Build configuration list for PBXNativeTarget "AVTagTextView" */ = { 551 | isa = XCConfigurationList; 552 | buildConfigurations = ( 553 | 2D4D934D1811DEBF00903297 /* Debug */, 554 | 2D4D934E1811DEBF00903297 /* Release */, 555 | ); 556 | defaultConfigurationIsVisible = 0; 557 | defaultConfigurationName = Release; 558 | }; 559 | 2D4D934F1811DEBF00903297 /* Build configuration list for PBXNativeTarget "AVTagTextViewTests" */ = { 560 | isa = XCConfigurationList; 561 | buildConfigurations = ( 562 | 2D4D93501811DEBF00903297 /* Debug */, 563 | 2D4D93511811DEBF00903297 /* Release */, 564 | ); 565 | defaultConfigurationIsVisible = 0; 566 | defaultConfigurationName = Release; 567 | }; 568 | /* End XCConfigurationList section */ 569 | }; 570 | rootObject = 2D4D93121811DEBF00903297 /* Project object */; 571 | } 572 | -------------------------------------------------------------------------------- /Project/AVTagTextView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Project/AVTagTextView.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Project/AVTagTextView/AVAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AVAppDelegate.h 3 | // AVTagTextView 4 | // 5 | // Created by Arseniy Vershinin on 9/13/13. 6 | // 7 | 8 | #import 9 | 10 | @interface AVAppDelegate : UIResponder 11 | 12 | @property (strong, nonatomic) UIWindow *window; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Project/AVTagTextView/AVAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AVAppDelegate.m 3 | // AVTagTextView 4 | // 5 | // Created by Arseniy Vershinin on 9/13/13. 6 | // 7 | 8 | #import "AVAppDelegate.h" 9 | 10 | @implementation AVAppDelegate 11 | 12 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 13 | { 14 | // Override point for customization after application launch. 15 | return YES; 16 | } 17 | 18 | - (void)applicationWillResignActive:(UIApplication *)application 19 | { 20 | // 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. 21 | // 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. 22 | } 23 | 24 | - (void)applicationDidEnterBackground:(UIApplication *)application 25 | { 26 | // 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. 27 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 28 | } 29 | 30 | - (void)applicationWillEnterForeground:(UIApplication *)application 31 | { 32 | // 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. 33 | } 34 | 35 | - (void)applicationDidBecomeActive:(UIApplication *)application 36 | { 37 | // 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. 38 | } 39 | 40 | - (void)applicationWillTerminate:(UIApplication *)application 41 | { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /Project/AVTagTextView/AVCustomTagTableViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AVCustomTagTableViewController.h 3 | // AVTagTextView 4 | // 5 | // Created by Arseniy Vershinin on 10/15/13. 6 | // 7 | 8 | #import 9 | #import "AVTagTextView/AVHashTagsTableViewController.h" 10 | 11 | /** 12 | * An examplary implementation of the custom table view 13 | * controller for displaying the tags. The implementation 14 | * has to conform to the AVTagTableViewControllerProtocol. For the 15 | * sake of simplicity, the controller is being inherited 16 | * from the simpliest implementation of the protocol - 17 | * AVHashTagsTableViewController 18 | */ 19 | 20 | @interface AVCustomTagTableViewController : AVHashTagsTableViewController 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Project/AVTagTextView/AVCustomTagTableViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // AVCustomTagTableViewController.m 3 | // AVTagTextView 4 | // 5 | // Created by Arseniy Vershinin on 10/15/13. 6 | // 7 | 8 | #import "AVCustomTagTableViewController.h" 9 | 10 | #import 11 | 12 | @interface AVCustomTagTableViewController () 13 | 14 | @end 15 | 16 | @implementation AVCustomTagTableViewController 17 | 18 | //Add table view header for the sake of the example: 19 | - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{ 20 | return @"Tags:"; 21 | } 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /Project/AVTagTextView/AVCustomTagTableViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Project/AVTagTextView/AVTagTextView-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | org.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Project/AVTagTextView/AVTagTextView-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #endif 17 | -------------------------------------------------------------------------------- /Project/AVTagTextView/AVViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AVViewController.h 3 | // AVTagTextView 4 | // 5 | // Created by Arseniy Vershinin on 9/13/13. 6 | // 7 | 8 | #import 9 | 10 | @interface AVViewController : UIViewController 11 | @property (strong, nonatomic) IBOutlet UITextView *textView; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Project/AVTagTextView/AVViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // AVViewController.m 3 | // AVTagTextView 4 | // 5 | // Created by Arseniy Vershinin on 9/13/13. 6 | // 7 | 8 | #import "AVViewController.h" 9 | 10 | #import 11 | #import "AVCustomTagTableViewController.h" 12 | 13 | @interface AVViewController () 14 | @property (nonatomic, strong) NSArray *tagsArray; 15 | 16 | @end 17 | 18 | @implementation AVViewController 19 | 20 | - (void)viewDidLoad 21 | { 22 | [super viewDidLoad]; 23 | // Do any additional setup after loading the view, typically from a nib. 24 | 25 | //Set up the tags array 26 | self.tagsArray = @[@"tag1", @"instagram", @"anothertag", @"hmm", @"tag3", @"tag4", @"tag5", @"tag6", @"tag7"]; 27 | 28 | //Set up the first text view with the default table view 29 | self.textView.delegate = self; 30 | self.textView.hashTagsDelegate = self; 31 | self.textView.hashTagsTableViewHeight = 120.; 32 | //Provide a custom implementation of the hash tags table view (optional) 33 | //self.textView.hashTagsTableViewController = [AVCustomTagTableViewController new]; 34 | } 35 | 36 | /** 37 | * As soon as the user enters some symbols after the last '#' sign, this method gets called. 38 | * Entered symbols are contained in the query string. It is your responsibility to provide 39 | * the text view with the list of the corresponding hashtags 40 | */ 41 | - (NSArray *)tagsForQuery:(NSString *)query{ 42 | //Provide the list of tags, you wish to display to the user: 43 | 44 | //For example, return the tags, which start with the query string from the predefined array: 45 | NSPredicate *bPredicate = 46 | [NSPredicate predicateWithFormat:@"SELF beginswith[c] %@", query]; 47 | NSArray *array = [self.tagsArray filteredArrayUsingPredicate:bPredicate]; 48 | 49 | return array; 50 | } 51 | 52 | - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text{ 53 | if([text isEqualToString:@"\n"]){ 54 | [textView resignFirstResponder]; 55 | 56 | //list all the contained tags: 57 | NSLog(@"Entered tags: %@", [self.textView.hashTags componentsJoinedByString:@" "]); 58 | } 59 | return YES; 60 | } 61 | 62 | - (void)didReceiveMemoryWarning 63 | { 64 | [super didReceiveMemoryWarning]; 65 | // Dispose of any resources that can be recreated. 66 | } 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /Project/AVTagTextView/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Project/AVTagTextView/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "40x40", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "60x60", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Project/AVTagTextView/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Project/AVTagTextView/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Project/AVTagTextView/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // AVTagTextView 4 | // 5 | // Created by Arseniy Vershinin on 10/18/13. 6 | // Copyright (c) 2013 Arseniy Vershinin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "AVAppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AVAppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Project/AVTagTextViewTests/AVTagTextViewTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | org.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Project/AVTagTextViewTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Project/Podfile: -------------------------------------------------------------------------------- 1 | pod 'AVTagTextView', :path => '../' 2 | target :AVTagTextViewTests, :exclusive => true do 3 | pod 'Kiwi/XCTest' 4 | pod 'ReactiveCocoa' 5 | end 6 | -------------------------------------------------------------------------------- /Project/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - AVTagTextView (0.1.0): 3 | - ReactiveCocoa 4 | - Kiwi/ARC (2.2.2) 5 | - Kiwi/NonARC (2.2.2) 6 | - Kiwi/XCTest (2.2.2): 7 | - Kiwi/ARC 8 | - Kiwi/NonARC 9 | - ReactiveCocoa (2.1.3): 10 | - ReactiveCocoa/Core 11 | - ReactiveCocoa/no-arc 12 | - ReactiveCocoa/Core (2.1.3): 13 | - ReactiveCocoa/no-arc 14 | - ReactiveCocoa/no-arc (2.1.3) 15 | 16 | DEPENDENCIES: 17 | - AVTagTextView (from `../`) 18 | - Kiwi/XCTest 19 | - ReactiveCocoa 20 | 21 | EXTERNAL SOURCES: 22 | AVTagTextView: 23 | :path: ../ 24 | 25 | SPEC CHECKSUMS: 26 | AVTagTextView: ac14f248f921cefd7a99cf2bd257a343fa01716e 27 | Kiwi: 03539aeb004ccd42bdbc31253617b26364315df4 28 | ReactiveCocoa: 0142b823f4737effe6100f9fbb0a43bdcb629aae 29 | 30 | COCOAPODS: 0.26.2 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AVTagTextView 2 | 3 | [![Version](http://cocoapod-badges.herokuapp.com/v/AVTagTextView/badge.png)](http://cocoadocs.org/docsets/AVTagTextView) 4 | [![Platform](http://cocoapod-badges.herokuapp.com/p/AVTagTextView/badge.png)](http://cocoadocs.org/docsets/AVTagTextView) 5 | 6 | A category that adds an instragram-like hashtag choosing/listing capability to the UITextView. 7 | 8 | ![Screencapture GIF](https://dl.dropboxusercontent.com/u/31058381/OpenSource/AVTagTextView/out.gif) 9 | 10 | ## Installation 11 | 12 | AVTagTextView is available through [CocoaPods](http://cocoapods.org), to install 13 | it simply add the following line to your Podfile: 14 | 15 | pod "AVTagTextView" 16 | 17 | Alternatively you can drop the "Classes" folder into your project that should have the [ReactiveCocoa](https://github.com/ReactiveCocoa/ReactiveCocoa) library installed 18 | 19 | Import the category: 20 | 21 | ```objc 22 | #import 23 | ``` 24 | 25 | ## Usage 26 | 27 | Assign the hashTagsDelegate property to the desired delegate and implement the tagsForQuery method: 28 | 29 | ```objc 30 | self.textView.hashTagsDelegate = self; 31 | //... 32 | 33 | /** 34 | * As soon as the user enters some symbols after the last '#' sign, this method gets called. 35 | * Entered symbols are contained in the query string. It is your responsibility to provide 36 | * the text view with the list of the corresponding hashtags as an array of strings: 37 | */ 38 | - (NSArray *)tagsForQuery:(NSString *)query{ 39 | //Provide the list of the tag, you wish to display to the user: 40 | 41 | //For example, return the tags, which start with the query string, from the predefined array: 42 | NSPredicate *bPredicate = 43 | [NSPredicate predicateWithFormat:@"SELF beginswith[c] %@", query]; 44 | NSArray *array = [self.tagsArray filteredArrayUsingPredicate:bPredicate]; 45 | 46 | return array; 47 | } 48 | ``` 49 | 50 | Hashtags, contained in the UITextView, can be accessed through its hashTags property: 51 | 52 | ```objc 53 | self.textView.hashTags //returns an array of strings, corresponding to the hash tags, found in the UITextView's text 54 | ``` 55 | 56 | The default UITableViewController that displays the tags can be replaced with your custom UITableViewController implementation that has to conform to the [AVTagTableViewProtocol](https://github.com/arsonic/AVTagTextView/blob/master/Classes/AVTagTableViewProtocol.h). See the exemplary [AVCustomTagTableViewController](https://github.com/arsonic/AVTagTextView/blob/master/Project/AVCustomTagTableViewController.h) class. 57 | 58 | ## Example 59 | 60 | To run the example project; clone the repo, and run `pod install` from the Project directory first. 61 | 62 | ## Requirements 63 | 64 | + [ReactiveCocoa](https://github.com/ReactiveCocoa/ReactiveCocoa). The category strongly relies on the capabilities of ReactiveCocoa library to react on the user's input and to properly handle the keyboard's hiding/showing notifications. 65 | + iOS 6.1 or greater 66 | 67 | ## Tests 68 | 69 | The project includes a [Kiwi](https://github.com/allending/Kiwi) spec that can be found in the Tests folder 70 | 71 | ## TODO 72 | 73 | 1. Remove ReactiveCocoa as a dependency. 74 | 75 | ## Author 76 | 77 | Arseniy Vershinin 78 | 79 | ## License 80 | 81 | AVTagTextView is available under the MIT license. See the LICENSE file for more info. 82 | 83 | 84 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | desc "Runs the specs [EMPTY]" 2 | task :spec do 3 | # Provide your own implementation 4 | end 5 | 6 | task :version do 7 | git_remotes = `git remote`.strip.split("\n") 8 | 9 | if git_remotes.count > 0 10 | puts "-- fetching version number from github" 11 | sh 'git fetch' 12 | 13 | remote_version = remote_spec_version 14 | end 15 | 16 | if remote_version.nil? 17 | puts "There is no current released version. You're about to release a new Pod." 18 | version = "0.0.1" 19 | else 20 | puts "The current released version of your pod is " + remote_spec_version.to_s() 21 | version = suggested_version_number 22 | end 23 | 24 | puts "Enter the version you want to release (" + version + ") " 25 | new_version_number = $stdin.gets.strip 26 | if new_version_number == "" 27 | new_version_number = version 28 | end 29 | 30 | replace_version_number(new_version_number) 31 | end 32 | 33 | desc "Release a new version of the Pod" 34 | task :release do 35 | 36 | puts "* Running version" 37 | sh "rake version" 38 | 39 | unless ENV['SKIP_CHECKS'] 40 | if `git symbolic-ref HEAD 2>/dev/null`.strip.split('/').last != 'master' 41 | $stderr.puts "[!] You need to be on the `master' branch in order to be able to do a release." 42 | exit 1 43 | end 44 | 45 | if `git tag`.strip.split("\n").include?(spec_version) 46 | $stderr.puts "[!] A tag for version `#{spec_version}' already exists. Change the version in the podspec" 47 | exit 1 48 | end 49 | 50 | puts "You are about to release `#{spec_version}`, is that correct? [y/n]" 51 | exit if $stdin.gets.strip.downcase != 'y' 52 | end 53 | 54 | puts "* Running specs" 55 | sh "rake spec" 56 | 57 | puts "* Linting the podspec" 58 | sh "pod lib lint" 59 | 60 | # Then release 61 | sh "git commit #{podspec_path} CHANGELOG.md -m 'Release #{spec_version}'" 62 | sh "git tag -a #{spec_version} -m 'Release #{spec_version}'" 63 | sh "git push origin master" 64 | sh "git push origin --tags" 65 | sh "pod push master #{podspec_path}" 66 | end 67 | 68 | # @return [Pod::Version] The version as reported by the Podspec. 69 | # 70 | def spec_version 71 | require 'cocoapods' 72 | spec = Pod::Specification.from_file(podspec_path) 73 | spec.version 74 | end 75 | 76 | # @return [Pod::Version] The version as reported by the Podspec from remote. 77 | # 78 | def remote_spec_version 79 | require 'cocoapods-core' 80 | 81 | if spec_file_exist_on_remote? 82 | remote_spec = eval(`git show origin/master:#{podspec_path}`) 83 | remote_spec.version 84 | else 85 | nil 86 | end 87 | end 88 | 89 | # @return [Bool] If the remote repository has a copy of the podpesc file or not. 90 | # 91 | def spec_file_exist_on_remote? 92 | test_condition = `if git rev-parse --verify --quiet origin/master:#{podspec_path} >/dev/null; 93 | then 94 | echo 'true' 95 | else 96 | echo 'false' 97 | fi` 98 | 99 | 'true' == test_condition.strip 100 | end 101 | 102 | # @return [String] The relative path of the Podspec. 103 | # 104 | def podspec_path 105 | podspecs = Dir.glob('*.podspec') 106 | if podspecs.count == 1 107 | podspecs.first 108 | else 109 | raise "Could not select a podspec" 110 | end 111 | end 112 | 113 | # @return [String] The suggested version number based on the local and remote version numbers. 114 | # 115 | def suggested_version_number 116 | if spec_version != remote_spec_version 117 | spec_version.to_s() 118 | else 119 | next_version(spec_version).to_s() 120 | end 121 | end 122 | 123 | # @param [Pod::Version] version 124 | # the version for which you need the next version 125 | # 126 | # @note It is computed by bumping the last component of the versino string by 1. 127 | # 128 | # @return [Pod::Version] The version that comes next after the version supplied. 129 | # 130 | def next_version(version) 131 | version_components = version.to_s().split("."); 132 | last = (version_components.last.to_i() + 1).to_s 133 | version_components[-1] = last 134 | Pod::Version.new(version_components.join(".")) 135 | end 136 | 137 | # @param [String] new_version_number 138 | # the new version number 139 | # 140 | # @note This methods replaces the version number in the podspec file with a new version number. 141 | # 142 | # @return void 143 | # 144 | def replace_version_number(new_version_number) 145 | text = File.read(podspec_path) 146 | text.gsub!(/(s.version( )*= ")#{spec_version}(")/, "\\1#{new_version_number}\\3") 147 | File.open(podspec_path, "w") { |file| file.puts text } 148 | end -------------------------------------------------------------------------------- /Tests/AVTagTextViewSpec.m: -------------------------------------------------------------------------------- 1 | // 2 | // AVTagTextViewSpec.m 3 | // AVTagTextViewSpec 4 | // 5 | // Created by Arseniy Vershinin on 9/13/13. 6 | // 7 | 8 | #import 9 | 10 | #import "UITextView+AVTagTextView.h" 11 | #import "AVHashTagsTableViewController.h" 12 | 13 | #import 14 | #import 15 | 16 | #define DEFAULT_KEYBOARD_SIZE 216. 17 | 18 | SPEC_BEGIN(AVTagTextViewSpec) 19 | 20 | describe(@"AVTagTextView", ^{ 21 | 22 | __block UITextView *textView = nil; 23 | 24 | beforeEach(^{ 25 | textView = [UITextView new]; 26 | }); 27 | 28 | it(@"should contain all the hash tagged words", ^{ 29 | textView.text = @"this# ##is#tag # a #tag string"; 30 | 31 | NSArray *hashTags = @[@"is", @"tag", @"tag"]; 32 | [[textView.hashTags should] equal:hashTags]; 33 | }); 34 | 35 | context(@"when user types in text after # symbol", ^{ 36 | 37 | __block NSObject *hashTagsDelegate = nil; 38 | 39 | beforeEach(^{ 40 | hashTagsDelegate = [KWMock mockForProtocol:@protocol(AVTagTextViewDelegate)]; 41 | textView.hashTagsDelegate = hashTagsDelegate; 42 | textView.text = @"text"; 43 | }); 44 | 45 | it(@"should associate a hash tags deleagate with the text view", ^{ 46 | [[(NSObject *)textView.hashTagsDelegate should] equal:hashTagsDelegate]; 47 | }); 48 | 49 | it(@"should request hashtags when last hashtag text changes", ^{ 50 | textView.text = @"no tags so far #yet#ueee"; 51 | [[hashTagsDelegate should] receive:@selector(tagsForQuery:) andReturn:nil withArguments:@"ueeett"]; 52 | [textView simulteUserInput:@"tt"]; 53 | }); 54 | 55 | it(@"shouldn't request hashtags when entered text doesnt belong to a hashtag", ^{ 56 | textView.text = @"some #text and #some #more"; 57 | [[hashTagsDelegate shouldNot] receive:@selector(tagsForQuery:)]; 58 | [textView simulteUserInput:@" tt"]; 59 | }); 60 | 61 | context(@"a table view with hashtags should be shown so that", ^{ 62 | 63 | __block NSArray *foundHashTags = @[@"tag1", @"tag2", @"tag3"]; 64 | __block UITableViewController *tableViewController; 65 | 66 | beforeEach(^{ 67 | [hashTagsDelegate stub:@selector(tagsForQuery:) andReturn:foundHashTags]; 68 | tableViewController = [AVHashTagsTableViewController new]; 69 | textView.hashTagsTableViewController = tableViewController; 70 | }); 71 | 72 | it(@"is filled with hashtags, provided by the delegate", ^{ 73 | [textView simulteUserInput:@" #tag"]; 74 | 75 | [[tableViewController shouldNot] beNil]; 76 | [[theValue([tableViewController tableView:tableViewController.tableView numberOfRowsInSection:0]) should] equal:theValue(foundHashTags.count)]; 77 | [[theValue(tableViewController.view.hidden) should] beFalse]; 78 | }); 79 | 80 | it(@"should be hide hidden when no hashtags were provided by the delegate", ^{ 81 | [hashTagsDelegate stub:@selector(tagsForQuery:) andReturn:nil]; 82 | [textView simulteUserInput:@" #tag"]; 83 | 84 | [[theValue(textView.hashTagsTableViewController.view.hidden) should] beTrue]; 85 | }); 86 | 87 | it(@"should be hidden when the input string doesn't end with a hashtag", ^{ 88 | tableViewController.view.hidden = NO; 89 | [textView simulteUserInput:@"#tag r"]; 90 | [[theValue(tableViewController.view.hidden) should] beTrue]; 91 | }); 92 | 93 | it(@"gets hidden when the keyboard is being hidden", ^{ 94 | [[NSNotificationCenter defaultCenter] postNotificationName:UIKeyboardWillHideNotification object:nil]; 95 | 96 | [[theValue(textView.hashTagsTableViewController.view.hidden) shouldEventually] beTrue]; 97 | }); 98 | 99 | it(@"should has a proper frame set up when the keyboard is being shown the first time", ^{ 100 | textView.hashTagsTableViewHeight = 200.; 101 | 102 | CGRect keyboardRect = CGRectMake(0., 264., 320., 216.); 103 | NSValue *keyboardSizeValue = [NSValue valueWithCGRect:keyboardRect]; 104 | [[NSNotificationCenter defaultCenter] postNotificationName:UIKeyboardDidShowNotification 105 | object:nil 106 | userInfo:@{UIKeyboardFrameBeginUserInfoKey: keyboardSizeValue}]; 107 | 108 | [[theValue(tableViewController.tableView.frame) should] equal:theValue(CGRectMake(0., keyboardRect.origin.y - keyboardRect.size.height - textView.hashTagsTableViewHeight, 320., textView.hashTagsTableViewHeight))]; 109 | }); 110 | 111 | it(@"should replace the last edited tag with the selected one when a cell gets tapped and hide itself", ^{ 112 | [textView simulteUserInput:@" #tag"]; 113 | 114 | NSIndexPath *selectedIndexPath = [NSIndexPath indexPathForRow:1 inSection:0]; 115 | [textView.hashTagsTableViewController tableView:textView.hashTagsTableViewController.tableView didSelectRowAtIndexPath:selectedIndexPath]; 116 | 117 | //TODO: the trailing white space doesnt get picked up by the text property (should be "text #tag2 ") 118 | [[textView.text should] equal:@"text #tag2 "]; 119 | [[[textView.hashTags lastObject] should] equal:@"tag2"]; 120 | [[theValue(tableViewController.view.hidden) should] beTrue]; 121 | }); 122 | 123 | }); 124 | 125 | }); 126 | 127 | }); 128 | 129 | SPEC_END --------------------------------------------------------------------------------