├── screenshot.jpg ├── screenshot.png ├── ContactPicker ├── en.lproj │ └── InfoPlist.strings ├── Default.png ├── Default@2x.png ├── Default-568h@2x.png ├── icon-avatar-60x60.png ├── icon-avatar-60x60@2x.png ├── icon-checkbox-unselected-25x25.png ├── icon-checkbox-selected-green-25x25.png ├── icon-checkbox-unselected-25x25@2x.png ├── icon-checkbox-selected-green-25x25@2x.png ├── THContactPickerTableViewCell.h ├── AppDelegate.h ├── ContactPicker-Prefix.pch ├── main.m ├── THBubbleColor.h ├── THContactPickerTableViewCell.m ├── THBubbleColor.m ├── THContact.h ├── THContactPickerViewController.h ├── THContactPickerView.h ├── THContactPickerViewController.xib ├── THContactBubble.h ├── ContactPicker-Info.plist ├── THContact.m ├── AppDelegate.m ├── THContactPickerTableViewCell.xib ├── THContactBubble.m ├── THContactPickerView.m └── THContactPickerViewController.m ├── .gitignore ├── README.md ├── LICENSE └── ContactPicker.xcodeproj └── project.pbxproj /screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soofdev/THContactPicker/HEAD/screenshot.jpg -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soofdev/THContactPicker/HEAD/screenshot.png -------------------------------------------------------------------------------- /ContactPicker/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /ContactPicker/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soofdev/THContactPicker/HEAD/ContactPicker/Default.png -------------------------------------------------------------------------------- /ContactPicker/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soofdev/THContactPicker/HEAD/ContactPicker/Default@2x.png -------------------------------------------------------------------------------- /ContactPicker/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soofdev/THContactPicker/HEAD/ContactPicker/Default-568h@2x.png -------------------------------------------------------------------------------- /ContactPicker/icon-avatar-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soofdev/THContactPicker/HEAD/ContactPicker/icon-avatar-60x60.png -------------------------------------------------------------------------------- /ContactPicker/icon-avatar-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soofdev/THContactPicker/HEAD/ContactPicker/icon-avatar-60x60@2x.png -------------------------------------------------------------------------------- /ContactPicker/icon-checkbox-unselected-25x25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soofdev/THContactPicker/HEAD/ContactPicker/icon-checkbox-unselected-25x25.png -------------------------------------------------------------------------------- /ContactPicker/icon-checkbox-selected-green-25x25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soofdev/THContactPicker/HEAD/ContactPicker/icon-checkbox-selected-green-25x25.png -------------------------------------------------------------------------------- /ContactPicker/icon-checkbox-unselected-25x25@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soofdev/THContactPicker/HEAD/ContactPicker/icon-checkbox-unselected-25x25@2x.png -------------------------------------------------------------------------------- /ContactPicker/icon-checkbox-selected-green-25x25@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soofdev/THContactPicker/HEAD/ContactPicker/icon-checkbox-selected-green-25x25@2x.png -------------------------------------------------------------------------------- /ContactPicker/THContactPickerTableViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // THContactPickerTableViewCell.h 3 | // ContactPicker 4 | // 5 | // Created by Mac on 3/27/14. 6 | // Copyright (c) 2014 Tristan Himmelman. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface THContactPickerTableViewCell : UITableViewCell 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ContactPicker/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // ContactPicker 4 | // 5 | // Created by Tristan Himmelman on 11/2/12. 6 | // Copyright (c) 2012 Tristan Himmelman. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | build/* 3 | *.pbxuser 4 | !default.pbxuser 5 | *.mode1v3 6 | !default.mode1v3 7 | *.mode2v3 8 | !default.mode2v3 9 | *.perspectivev3 10 | !default.perspectivev3 11 | *.xcworkspace 12 | !default.xcworkspace 13 | xcuserdata 14 | profile 15 | *.moved-aside 16 | 17 | # Exclude temp nibs and swap files 18 | *~.nib 19 | *.swp 20 | 21 | # Exclude OS X folder attributes 22 | .DS_Store 23 | -------------------------------------------------------------------------------- /ContactPicker/ContactPicker-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'ContactPicker' target in the 'ContactPicker' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_3_0 8 | #warning "This project uses features only available in iOS SDK 3.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #endif 15 | -------------------------------------------------------------------------------- /ContactPicker/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // ContactPicker 4 | // 5 | // Created by Tristan Himmelman on 11/2/12. 6 | // Copyright (c) 2012 Tristan Himmelman. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "AppDelegate.h" 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ContactPicker/THBubbleColor.h: -------------------------------------------------------------------------------- 1 | // 2 | // THBubbleColor.h 3 | // ContactPicker 4 | // 5 | // Created by Dmitry Vorobjov on 12/6/12. 6 | // Copyright (c) 2012 Tristan Himmelman. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface THBubbleColor : NSObject 12 | 13 | @property (nonatomic, strong) UIColor *gradientTop; 14 | @property (nonatomic, strong) UIColor *gradientBottom; 15 | @property (nonatomic, strong) UIColor *border; 16 | 17 | - (id)initWithGradientTop:(UIColor *)gradientTop gradientBottom:(UIColor *)gradientBottom border:(UIColor *)border; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /ContactPicker/THContactPickerTableViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // THContactPickerTableViewCell.m 3 | // ContactPicker 4 | // 5 | // Created by Mac on 3/27/14. 6 | // Copyright (c) 2014 Tristan Himmelman. All rights reserved. 7 | // 8 | 9 | #import "THContactPickerTableViewCell.h" 10 | 11 | @implementation THContactPickerTableViewCell 12 | 13 | - (void)awakeFromNib 14 | { 15 | // Initialization code 16 | } 17 | 18 | - (void)setSelected:(BOOL)selected animated:(BOOL)animated 19 | { 20 | [super setSelected:selected animated:animated]; 21 | 22 | // Configure the view for the selected state 23 | } 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /ContactPicker/THBubbleColor.m: -------------------------------------------------------------------------------- 1 | // 2 | // THBubbleColor.m 3 | // ContactPicker 4 | // 5 | // Created by Dmitry Vorobjov on 12/6/12. 6 | // Copyright (c) 2012 Tristan Himmelman. All rights reserved. 7 | // 8 | 9 | #import "THBubbleColor.h" 10 | 11 | @implementation THBubbleColor 12 | 13 | - (id)initWithGradientTop:(UIColor *)gradientTop gradientBottom:(UIColor *)gradientBottom border:(UIColor *)border { 14 | if (self = [super init]) { 15 | self.gradientTop = gradientTop; 16 | self.gradientBottom = gradientBottom; 17 | self.border = border; 18 | } 19 | return self; 20 | } 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /ContactPicker/THContact.h: -------------------------------------------------------------------------------- 1 | // 2 | // Contact.h 3 | // upsi 4 | // 5 | // Created by Mac on 3/24/14. 6 | // Copyright (c) 2014 Laith. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface THContact : NSObject 12 | 13 | - (instancetype)initWithAttributes:(NSDictionary *)attributes; 14 | - (NSString *)fullName; 15 | 16 | @property (nonatomic, assign) NSInteger recordId; 17 | @property (nonatomic, strong) NSString *firstName; 18 | @property (nonatomic, strong) NSString *lastName; 19 | @property (nonatomic, assign) NSString *phone; 20 | @property (nonatomic, strong) NSString *email; 21 | @property (nonatomic, strong) UIImage *image; 22 | @property (nonatomic, assign, getter = isSelected) BOOL selected; 23 | @property (nonatomic, strong) NSDate *date; 24 | @property (nonatomic, strong) NSDate *dateUpdated; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /ContactPicker/THContactPickerViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ContactPickerViewController.h 3 | // ContactPicker 4 | // 5 | // Created by Tristan Himmelman on 11/2/12. 6 | // Copyright (c) 2012 Tristan Himmelman. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "THContactPickerView.h" 12 | #import "THContactPickerTableViewCell.h" 13 | 14 | @interface THContactPickerViewController : UIViewController 15 | 16 | @property (nonatomic, strong) THContactPickerView *contactPickerView; 17 | @property (nonatomic, strong) UITableView *tableView; 18 | @property (nonatomic, strong) NSArray *contacts; 19 | @property (nonatomic, strong) NSMutableArray *selectedContacts; 20 | @property (nonatomic, strong) NSArray *filteredContacts; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | THContactPicker 2 | =============== 3 | 4 | An iOS view used for selecting multiple contacts. This was built to mimic the selecting contact functionality in the Apple Mail app with improved UI. 5 | 6 | Details: 7 | 8 | - Control now loads contacts from address book after requesting permission. 9 | - Added model class THContact 10 | - Used custom cell view for easier UI customization. 11 | - Added neat circular checkbox to the left side of the contact cell. 12 | - Added circular contact images. 13 | - Text filter field and table view resize using animation for smoother feel. 14 | - Bar button on the top right is disabled by default and enabled when there is at least 1 contact selected. 15 | - Keyboard is dismissed when tapping outside the filter text field. 16 | - TODO: Implement contact details view 17 | 18 | Special thanks to Pavel Dušátko (@dusi) for the mentorship while working on this. 19 | 20 | ![Screenshot](https://raw.github.com/soofani/THContactPicker/master/screenshot.png) 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2012 Tristan Himmelman 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /ContactPicker/THContactPickerView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ContactPickerTextView.h 3 | // ContactPicker 4 | // 5 | // Created by Tristan Himmelman on 11/2/12. 6 | // Copyright (c) 2012 Tristan Himmelman. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "THContactBubble.h" 11 | 12 | @class THContactPickerView; 13 | 14 | @protocol THContactPickerDelegate 15 | 16 | - (void)contactPickerTextViewDidChange:(NSString *)textViewText; 17 | - (void)contactPickerDidRemoveContact:(id)contact; 18 | - (void)contactPickerDidResize:(THContactPickerView *)contactPickerView; 19 | 20 | @end 21 | 22 | @interface THContactPickerView : UIView 23 | 24 | @property (nonatomic, strong) THContactBubble *selectedContactBubble; 25 | @property (nonatomic, assign) IBOutlet id delegate; 26 | @property (nonatomic, assign) BOOL limitToOne; 27 | @property (nonatomic, assign) CGFloat viewPadding; 28 | @property (nonatomic, strong) UIFont *font; 29 | 30 | - (void)addContact:(id)contact withName:(NSString *)name; 31 | - (void)removeContact:(id)contact; 32 | - (void)removeAllContacts; 33 | - (void)setPlaceholderString:(NSString *)placeholderString; 34 | - (void)disableDropShadow; 35 | - (void)resignKeyboard; 36 | - (void)setBubbleColor:(THBubbleColor *)color selectedColor:(THBubbleColor *)selectedColor; 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /ContactPicker/THContactPickerViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ContactPicker/THContactBubble.h: -------------------------------------------------------------------------------- 1 | // 2 | // THContactBubble.h 3 | // ContactPicker 4 | // 5 | // Created by Tristan Himmelman on 11/2/12. 6 | // Copyright (c) 2012 Tristan Himmelman. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "THBubbleColor.h" 12 | 13 | @class THContactBubble; 14 | 15 | @protocol THContactBubbleDelegate 16 | 17 | - (void)contactBubbleWasSelected:(THContactBubble *)contactBubble; 18 | - (void)contactBubbleWasUnSelected:(THContactBubble *)contactBubble; 19 | - (void)contactBubbleShouldBeRemoved:(THContactBubble *)contactBubble; 20 | 21 | @end 22 | 23 | @interface THContactBubble : UIView 24 | 25 | @property (nonatomic, strong) NSString *name; 26 | @property (nonatomic, strong) UILabel *label; 27 | @property (nonatomic, strong) UITextView *textView; // used to capture keyboard touches when view is selected 28 | @property (nonatomic, assign) BOOL isSelected; 29 | @property (nonatomic, assign) id delegate; 30 | @property (nonatomic, strong) CAGradientLayer *gradientLayer; 31 | 32 | @property (nonatomic, strong) THBubbleColor *color; 33 | @property (nonatomic, strong) THBubbleColor *selectedColor; 34 | 35 | - (id)initWithName:(NSString *)name; 36 | - (id)initWithName:(NSString *)name 37 | color:(THBubbleColor *)color 38 | selectedColor:(THBubbleColor *)selectedColor; 39 | 40 | - (void)select; 41 | - (void)unSelect; 42 | - (void)setFont:(UIFont *)font; 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /ContactPicker/ContactPicker-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | ContactPicker.${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 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ContactPicker/THContact.m: -------------------------------------------------------------------------------- 1 | // 2 | // Contact.m 3 | // upsi 4 | // 5 | // Created by Mac on 3/24/14. 6 | // Copyright (c) 2014 Laith. All rights reserved. 7 | // 8 | 9 | #import "THContact.h" 10 | 11 | @implementation THContact 12 | 13 | #pragma mark - NSObject - Creating, Copying, and Deallocating Objects 14 | 15 | - (instancetype)initWithAttributes:(NSDictionary *)attributes { 16 | self = [super init]; 17 | if (self) { 18 | [self setValuesForKeysWithDictionary:attributes]; 19 | } 20 | 21 | return self; 22 | } 23 | 24 | #pragma mark - NSKeyValueCoding Protocol 25 | 26 | - (void)setValue:(id)value forKey:(NSString *)key { 27 | if ([key isEqualToString:@"id"]) { 28 | self.recordId = [value integerValue]; 29 | } else if ([key isEqualToString:@"firstName"]) { 30 | self.firstName = value; 31 | } else if ([key isEqualToString:@"lastName"]) { 32 | self.lastName = value; 33 | } else if ([key isEqualToString:@"phone"]) { 34 | self.phone = value; 35 | }else if ([key isEqualToString:@"email"]) { 36 | self.email = value; 37 | }else if ([key isEqualToString:@"image"]) { 38 | self.image = value; 39 | }else if ([key isEqualToString:@"isSelected"]) { 40 | self.selected = [value boolValue]; 41 | }else if ([key isEqualToString:@"date"]) { 42 | // TODO: Fix 43 | NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 44 | dateFormatter.dateFormat = @"yyyy-MM-dd"; 45 | self.date = [dateFormatter dateFromString:value]; 46 | } else if ([key isEqualToString:@"dateUpdated"]) { 47 | NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 48 | dateFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ"; 49 | self.dateUpdated = [dateFormatter dateFromString:value]; 50 | } 51 | } 52 | 53 | - (NSString *)fullName { 54 | if(self.firstName != nil && self.lastName != nil) { 55 | return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName]; 56 | } else if (self.firstName != nil) { 57 | return self.firstName; 58 | } else if (self.lastName != nil) { 59 | return self.lastName; 60 | } else { 61 | return @""; 62 | } 63 | } 64 | 65 | @end 66 | -------------------------------------------------------------------------------- /ContactPicker/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // ContactPicker 4 | // 5 | // Created by Tristan Himmelman on 11/2/12. 6 | // Copyright (c) 2012 Tristan Himmelman. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "THContactPickerViewController.h" 11 | 12 | @implementation AppDelegate 13 | 14 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 15 | { 16 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 17 | // Override point for customization after application launch. 18 | self.window.backgroundColor = [UIColor whiteColor]; 19 | 20 | 21 | THContactPickerViewController *contactPicker = [[THContactPickerViewController alloc] initWithNibName:@"THContactPickerViewController" bundle:nil]; 22 | UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:contactPicker]; 23 | 24 | self.window.rootViewController = navigationController; 25 | 26 | [self.window makeKeyAndVisible]; 27 | 28 | return YES; 29 | } 30 | 31 | - (void)applicationWillResignActive:(UIApplication *)application 32 | { 33 | // 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. 34 | // 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. 35 | } 36 | 37 | - (void)applicationDidEnterBackground:(UIApplication *)application 38 | { 39 | // 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. 40 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 41 | } 42 | 43 | - (void)applicationWillEnterForeground:(UIApplication *)application 44 | { 45 | // 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. 46 | } 47 | 48 | - (void)applicationDidBecomeActive:(UIApplication *)application 49 | { 50 | // 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. 51 | } 52 | 53 | - (void)applicationWillTerminate:(UIApplication *)application 54 | { 55 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 56 | } 57 | 58 | @end 59 | -------------------------------------------------------------------------------- /ContactPicker/THContactPickerTableViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 24 | 25 | 26 | 27 | 28 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /ContactPicker/THContactBubble.m: -------------------------------------------------------------------------------- 1 | // 2 | // THContactBubble.m 3 | // ContactPicker 4 | // 5 | // Created by Tristan Himmelman on 11/2/12. 6 | // Copyright (c) 2012 Tristan Himmelman. All rights reserved. 7 | // 8 | 9 | #import "THContactBubble.h" 10 | 11 | @implementation THContactBubble 12 | 13 | #define kHorizontalPadding 10 14 | #define kVerticalPadding 2 15 | 16 | #define kBubbleColor [UIColor colorWithRed:24.0/255.0 green:134.0/255.0 blue:242.0/255.0 alpha:1.0] 17 | #define kBubbleColorSelected [UIColor colorWithRed:151.0/255.0f green:199.0/255.0f blue:250.0/255.0f alpha:1.0] 18 | 19 | - (id)initWithName:(NSString *)name { 20 | if ([self initWithName:name color:nil selectedColor:nil]) { 21 | 22 | } 23 | return self; 24 | } 25 | 26 | - (id)initWithName:(NSString *)name color:(THBubbleColor *)color selectedColor:(THBubbleColor *)selectedColor { 27 | self = [super init]; 28 | if (self){ 29 | self.name = name; 30 | self.isSelected = NO; 31 | 32 | if (color == nil){ 33 | color = [[THBubbleColor alloc] initWithGradientTop:kBubbleColor gradientBottom:kBubbleColor border:kBubbleColor]; 34 | } 35 | 36 | if (selectedColor == nil){ 37 | selectedColor = [[THBubbleColor alloc] initWithGradientTop:kBubbleColorSelected gradientBottom:kBubbleColorSelected border:kBubbleColorSelected]; 38 | } 39 | 40 | self.color = color; 41 | self.selectedColor = selectedColor; 42 | 43 | [self setupView]; 44 | } 45 | return self; 46 | } 47 | 48 | - (void)setupView { 49 | // Create Label 50 | self.label = [[UILabel alloc] init]; 51 | self.label.backgroundColor = [UIColor clearColor]; 52 | self.label.text = self.name; 53 | [self addSubview:self.label]; 54 | 55 | self.textView = [[UITextView alloc] init]; 56 | self.textView.delegate = self; 57 | self.textView.hidden = YES; 58 | [self addSubview:self.textView]; 59 | 60 | // Create a tap gesture recognizer 61 | UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture)]; 62 | tapGesture.numberOfTapsRequired = 1; 63 | tapGesture.numberOfTouchesRequired = 1; 64 | [self addGestureRecognizer:tapGesture]; 65 | 66 | [self adjustSize]; 67 | 68 | [self unSelect]; 69 | } 70 | 71 | - (void)adjustSize { 72 | // Adjust the label frames 73 | [self.label sizeToFit]; 74 | CGRect frame = self.label.frame; 75 | frame.origin.x = kHorizontalPadding; 76 | frame.origin.y = kVerticalPadding; 77 | self.label.frame = frame; 78 | 79 | // Adjust view frame 80 | self.bounds = CGRectMake(0, 0, frame.size.width + 2 * kHorizontalPadding, frame.size.height + 2 * kVerticalPadding); 81 | 82 | // Create gradient layer 83 | if (self.gradientLayer == nil){ 84 | self.gradientLayer = [CAGradientLayer layer]; 85 | [self.layer insertSublayer:self.gradientLayer atIndex:0]; 86 | } 87 | self.gradientLayer.frame = self.bounds; 88 | 89 | // Round the corners 90 | CALayer *viewLayer = [self layer]; 91 | viewLayer.cornerRadius = self.bounds.size.height / 2; 92 | viewLayer.borderWidth = 1; 93 | viewLayer.masksToBounds = YES; 94 | } 95 | 96 | - (void)setFont:(UIFont *)font { 97 | self.label.font = font; 98 | 99 | [self adjustSize]; 100 | } 101 | 102 | - (void)select { 103 | if ([self.delegate respondsToSelector:@selector(contactBubbleWasSelected:)]){ 104 | [self.delegate contactBubbleWasSelected:self]; 105 | } 106 | 107 | CALayer *viewLayer = [self layer]; 108 | viewLayer.borderColor = self.selectedColor.border.CGColor; 109 | 110 | self.gradientLayer.colors = [NSArray arrayWithObjects:(id)[self.selectedColor.gradientTop CGColor], (id)[self.selectedColor.gradientBottom CGColor], nil]; 111 | 112 | self.label.textColor = [UIColor whiteColor]; 113 | 114 | self.isSelected = YES; 115 | 116 | [self.textView becomeFirstResponder]; 117 | } 118 | 119 | - (void)unSelect { 120 | CALayer *viewLayer = [self layer]; 121 | viewLayer.borderColor = self.color.border.CGColor; 122 | 123 | self.gradientLayer.colors = [NSArray arrayWithObjects:(id)[self.color.gradientTop CGColor], (id)[self.color.gradientBottom CGColor], nil]; 124 | 125 | self.label.textColor = [UIColor whiteColor]; 126 | 127 | [self setNeedsDisplay]; 128 | self.isSelected = NO; 129 | 130 | [self.textView resignFirstResponder]; 131 | } 132 | 133 | - (void)handleTapGesture { 134 | if (self.isSelected){ 135 | [self unSelect]; 136 | } else { 137 | [self select]; 138 | } 139 | } 140 | 141 | #pragma mark - UITextViewDelegate 142 | 143 | - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text; 144 | { 145 | self.textView.hidden = NO; 146 | 147 | if ( [text isEqualToString:@"\n"] ) { // Return key was pressed 148 | return NO; 149 | } 150 | 151 | // Capture "delete" key press when cell is empty 152 | if ([textView.text isEqualToString:@""] && [text isEqualToString:@""]){ 153 | if ([self.delegate respondsToSelector:@selector(contactBubbleShouldBeRemoved:)]){ 154 | [self.delegate contactBubbleShouldBeRemoved:self]; 155 | } 156 | } 157 | 158 | if (self.isSelected){ 159 | self.textView.text = @""; 160 | [self unSelect]; 161 | if ([self.delegate respondsToSelector:@selector(contactBubbleWasUnSelected:)]){ 162 | [self.delegate contactBubbleWasUnSelected:self]; 163 | } 164 | } 165 | 166 | return YES; 167 | } 168 | 169 | /* 170 | // Only override drawRect: if you perform custom drawing. 171 | // An empty implementation adversely affects performance during animation. 172 | - (void)drawRect:(CGRect)rect 173 | { 174 | // Drawing code 175 | } 176 | */ 177 | 178 | @end 179 | -------------------------------------------------------------------------------- /ContactPicker/THContactPickerView.m: -------------------------------------------------------------------------------- 1 | // 2 | // ContactPickerTextView.m 3 | // ContactPicker 4 | // 5 | // Created by Tristan Himmelman on 11/2/12. 6 | // Copyright (c) 2012 Tristan Himmelman. All rights reserved. 7 | // 8 | 9 | #import "THContactPickerView.h" 10 | #import "THContactBubble.h" 11 | 12 | @interface THContactPickerView (){ 13 | BOOL _shouldSelectTextView; 14 | } 15 | 16 | @property (nonatomic, strong) UIScrollView *scrollView; 17 | @property (nonatomic, strong) NSMutableDictionary *contacts; 18 | @property (nonatomic, strong) NSMutableArray *contactKeys; // an ordered set of the keys placed in the contacts dictionary 19 | @property (nonatomic, strong) UILabel *placeholderLabel; 20 | @property (nonatomic, assign) CGFloat lineHeight; 21 | @property (nonatomic, strong) UITextView *textView; 22 | 23 | @property (nonatomic, strong) THBubbleColor *bubbleColor; 24 | @property (nonatomic, strong) THBubbleColor *bubbleSelectedColor; 25 | 26 | @end 27 | 28 | @implementation THContactPickerView 29 | 30 | #define kViewPadding 5 // the amount of padding on top and bottom of the view 31 | #define kHorizontalPadding 2 // the amount of padding to the left and right of each contact bubble 32 | #define kVerticalPadding 4 // amount of padding above and below each contact bubble 33 | #define kTextViewMinWidth 130 34 | 35 | - (id)initWithCoder:(NSCoder *)aDecoder { 36 | self = [super initWithCoder:aDecoder]; 37 | if (self){ 38 | [self setup]; 39 | } 40 | return self; 41 | } 42 | 43 | - (id)initWithFrame:(CGRect)frame 44 | { 45 | self = [super initWithFrame:frame]; 46 | if (self) { 47 | // Initialization code 48 | [self setup]; 49 | } 50 | return self; 51 | } 52 | 53 | - (void)setup { 54 | self.viewPadding = kViewPadding; 55 | 56 | self.contacts = [NSMutableDictionary dictionary]; 57 | self.contactKeys = [NSMutableArray array]; 58 | 59 | // Create a contact bubble to determine the height of a line 60 | THContactBubble *contactBubble = [[THContactBubble alloc] initWithName:@"Sample"]; 61 | self.lineHeight = contactBubble.frame.size.height + 2 * kVerticalPadding; 62 | 63 | self.scrollView = [[UIScrollView alloc] initWithFrame:self.bounds]; 64 | self.scrollView.scrollsToTop = NO; 65 | self.scrollView.delegate = self; 66 | [self addSubview:self.scrollView]; 67 | 68 | // Create TextView 69 | // It would make more sense to use a UITextField (because it doesnt wrap text), however, there is no easy way to detect the "delete" key press using a UITextField when there is no string in the field 70 | self.textView = [[UITextView alloc] init]; 71 | self.textView.delegate = self; 72 | self.textView.font = contactBubble.label.font; 73 | self.textView.backgroundColor = [UIColor clearColor]; 74 | self.textView.contentInset = UIEdgeInsetsMake(-4, -2, 0, 0); 75 | self.textView.scrollEnabled = NO; 76 | self.textView.scrollsToTop = NO; 77 | self.textView.clipsToBounds = NO; 78 | self.textView.autocorrectionType = UITextAutocorrectionTypeNo; 79 | //[self.textView becomeFirstResponder]; 80 | 81 | // Add shadow to bottom border 82 | self.backgroundColor = [UIColor whiteColor]; 83 | CALayer *layer = [self layer]; 84 | [layer setShadowColor:[[UIColor colorWithRed:225.0/255.0 green:226.0/255.0 blue:228.0/255.0 alpha:1] CGColor]]; 85 | [layer setShadowOffset:CGSizeMake(0, 2)]; 86 | [layer setShadowOpacity:1]; 87 | [layer setShadowRadius:1.0f]; 88 | 89 | // Add placeholder label 90 | self.placeholderLabel = [[UILabel alloc] initWithFrame:CGRectMake(8, self.viewPadding, self.frame.size.width, self.lineHeight)]; 91 | self.placeholderLabel.font = contactBubble.label.font; 92 | self.placeholderLabel.textColor = [UIColor grayColor]; 93 | self.placeholderLabel.backgroundColor = [UIColor clearColor]; 94 | [self addSubview:self.placeholderLabel]; 95 | 96 | UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture)]; 97 | tapGesture.numberOfTapsRequired = 1; 98 | tapGesture.numberOfTouchesRequired = 1; 99 | [self addGestureRecognizer:tapGesture]; 100 | } 101 | 102 | #pragma mark - Public functions 103 | 104 | - (void)disableDropShadow { 105 | CALayer *layer = [self layer]; 106 | [layer setShadowRadius:0]; 107 | [layer setShadowOpacity:0]; 108 | } 109 | 110 | - (void)setFont:(UIFont *)font { 111 | _font = font; 112 | // Create a contact bubble to determine the height of a line 113 | THContactBubble *contactBubble = [[THContactBubble alloc] initWithName:@"Sample"]; 114 | [contactBubble setFont:font]; 115 | self.lineHeight = contactBubble.frame.size.height + 2 * kVerticalPadding; 116 | 117 | self.textView.font = font; 118 | [self.textView sizeToFit]; 119 | 120 | self.placeholderLabel.font = font; 121 | self.placeholderLabel.frame = CGRectMake(8, self.viewPadding, self.frame.size.width, self.lineHeight); 122 | } 123 | 124 | - (void)addContact:(id)contact withName:(NSString *)name { 125 | id contactKey = [NSValue valueWithNonretainedObject:contact]; 126 | if ([self.contactKeys containsObject:contactKey]){ 127 | NSLog(@"Cannot add the same object twice to ContactPickerView"); 128 | return; 129 | } 130 | 131 | self.textView.text = @""; 132 | 133 | THContactBubble *contactBubble = [[THContactBubble alloc] initWithName:name color:self.bubbleColor selectedColor:self.bubbleSelectedColor]; 134 | if (self.font != nil){ 135 | [contactBubble setFont:self.font]; 136 | } 137 | contactBubble.delegate = self; 138 | [self.contacts setObject:contactBubble forKey:contactKey]; 139 | [self.contactKeys addObject:contactKey]; 140 | 141 | // update layout 142 | [self layoutView]; 143 | 144 | // scroll to bottom 145 | _shouldSelectTextView = YES; 146 | [self scrollToBottomWithAnimation:YES]; 147 | // after scroll animation [self selectTextView] will be called 148 | } 149 | 150 | - (void)selectTextView { 151 | self.textView.hidden = NO; 152 | //[self.textView becomeFirstResponder]; 153 | } 154 | 155 | - (void)removeAllContacts 156 | { 157 | for(id contact in [self.contacts allKeys]){ 158 | THContactBubble *contactBubble = [self.contacts objectForKey:contact]; 159 | [contactBubble removeFromSuperview]; 160 | } 161 | [self.contacts removeAllObjects]; 162 | [self.contactKeys removeAllObjects]; 163 | 164 | // update layout 165 | [self layoutView]; 166 | 167 | self.textView.hidden = NO; 168 | self.textView.text = @""; 169 | 170 | } 171 | 172 | - (void)removeContact:(id)contact { 173 | id contactKey = [NSValue valueWithNonretainedObject:contact]; 174 | // Remove contactBubble from view 175 | THContactBubble *contactBubble = [self.contacts objectForKey:contactKey]; 176 | [contactBubble removeFromSuperview]; 177 | 178 | // Remove contact from memory 179 | [self.contacts removeObjectForKey:contactKey]; 180 | [self.contactKeys removeObject:contactKey]; 181 | 182 | // update layout 183 | [self layoutView]; 184 | 185 | //[self.textView becomeFirstResponder]; 186 | self.textView.hidden = NO; 187 | self.textView.text = @""; 188 | 189 | [self scrollToBottomWithAnimation:NO]; 190 | } 191 | 192 | - (void)setPlaceholderString:(NSString *)placeholderString { 193 | self.placeholderLabel.text = placeholderString; 194 | 195 | [self layoutView]; 196 | } 197 | 198 | - (void)resignKeyboard { 199 | [self.textView resignFirstResponder]; 200 | } 201 | 202 | - (void)setViewPadding:(CGFloat)viewPadding { 203 | _viewPadding = viewPadding; 204 | 205 | [self layoutView]; 206 | } 207 | 208 | - (void)setBubbleColor:(THBubbleColor *)color selectedColor:(THBubbleColor *)selectedColor { 209 | self.bubbleColor = color; 210 | self.bubbleSelectedColor = selectedColor; 211 | 212 | for (id contactKey in self.contactKeys){ 213 | THContactBubble *contactBubble = (THContactBubble *)[self.contacts objectForKey:contactKey]; 214 | 215 | contactBubble.color = color; 216 | contactBubble.selectedColor = selectedColor; 217 | 218 | // thid stuff reloads bubble 219 | if (contactBubble.isSelected){ 220 | [contactBubble select]; 221 | } else { 222 | [contactBubble unSelect]; 223 | } 224 | } 225 | } 226 | 227 | #pragma mark - Private functions 228 | 229 | - (void)scrollToBottomWithAnimation:(BOOL)animated { 230 | if (animated){ 231 | CGSize size = self.scrollView.contentSize; 232 | CGRect frame = CGRectMake(0, size.height - self.scrollView.frame.size.height, size.width, self.scrollView.frame.size.height); 233 | 234 | [self.scrollView scrollRectToVisible:frame animated:animated]; 235 | } else { 236 | // this block is here because scrollRectToVisible with animated NO causes crashes on iOS 5 when the user tries to delete many contacts really quickly 237 | CGPoint offset = self.scrollView.contentOffset; 238 | offset.y = self.scrollView.contentSize.height - self.scrollView.frame.size.height; 239 | self.scrollView.contentOffset = offset; 240 | } 241 | } 242 | 243 | - (void)removeContactBubble:(THContactBubble *)contactBubble { 244 | id contact = [self contactForContactBubble:contactBubble]; 245 | if (contact == nil){ 246 | return; 247 | } 248 | 249 | if ([self.delegate respondsToSelector:@selector(contactPickerDidRemoveContact:)]){ 250 | [self.delegate contactPickerDidRemoveContact:[contact nonretainedObjectValue]]; 251 | } 252 | 253 | [self removeContactByKey:contact]; 254 | } 255 | 256 | - (void)removeContactByKey:(id)contactKey { 257 | // Remove contactBubble from view 258 | THContactBubble *contactBubble = [self.contacts objectForKey:contactKey]; 259 | [contactBubble removeFromSuperview]; 260 | 261 | // Remove contact from memory 262 | [self.contacts removeObjectForKey:contactKey]; 263 | [self.contactKeys removeObject:contactKey]; 264 | 265 | // update layout 266 | [self layoutView]; 267 | 268 | //[self.textView becomeFirstResponder]; 269 | self.textView.hidden = NO; 270 | self.textView.text = @""; 271 | 272 | [self scrollToBottomWithAnimation:NO]; 273 | } 274 | 275 | - (id)contactForContactBubble:(THContactBubble *)contactBubble { 276 | NSArray *keys = [self.contacts allKeys]; 277 | 278 | for (id contact in keys){ 279 | if ([[self.contacts objectForKey:contact] isEqual:contactBubble]){ 280 | return contact; 281 | } 282 | } 283 | return nil; 284 | } 285 | 286 | - (void)layoutView { 287 | CGRect frameOfLastBubble = CGRectNull; 288 | int lineCount = 0; 289 | 290 | // Loop through selectedContacts and position/add them to the view 291 | for (id contactKey in self.contactKeys){ 292 | THContactBubble *contactBubble = (THContactBubble *)[self.contacts objectForKey:contactKey]; 293 | CGRect bubbleFrame = contactBubble.frame; 294 | 295 | if (CGRectIsNull(frameOfLastBubble)){ // first line 296 | bubbleFrame.origin.x = kHorizontalPadding; 297 | bubbleFrame.origin.y = kVerticalPadding + self.viewPadding; 298 | } else { 299 | // Check if contact bubble will fit on the current line 300 | CGFloat width = bubbleFrame.size.width + 2 * kHorizontalPadding; 301 | if (self.frame.size.width - frameOfLastBubble.origin.x - frameOfLastBubble.size.width - width >= 0){ // add to the same line 302 | // Place contact bubble just after last bubble on the same line 303 | bubbleFrame.origin.x = frameOfLastBubble.origin.x + frameOfLastBubble.size.width + kHorizontalPadding * 2; 304 | bubbleFrame.origin.y = frameOfLastBubble.origin.y; 305 | } else { // No space on line, jump to next line 306 | lineCount++; 307 | bubbleFrame.origin.x = kHorizontalPadding; 308 | bubbleFrame.origin.y = (lineCount * self.lineHeight) + kVerticalPadding + self.viewPadding; 309 | } 310 | } 311 | frameOfLastBubble = bubbleFrame; 312 | contactBubble.frame = bubbleFrame; 313 | // Add contact bubble if it hasn't been added 314 | if (contactBubble.superview == nil){ 315 | [self.scrollView addSubview:contactBubble]; 316 | } 317 | } 318 | 319 | // Now add a textView after the comment bubbles 320 | CGFloat minWidth = kTextViewMinWidth + 2 * kHorizontalPadding; 321 | CGRect textViewFrame = CGRectMake(0, 0, self.textView.frame.size.width, self.lineHeight/* - 2 * kVerticalPadding*/); 322 | // Check if we can add the text field on the same line as the last contact bubble 323 | if (self.frame.size.width - frameOfLastBubble.origin.x - frameOfLastBubble.size.width - minWidth >= 0){ // add to the same line 324 | textViewFrame.origin.x = frameOfLastBubble.origin.x + frameOfLastBubble.size.width + kHorizontalPadding; 325 | textViewFrame.size.width = self.frame.size.width - textViewFrame.origin.x; 326 | } else { // place text view on the next line 327 | lineCount++; 328 | 329 | textViewFrame.origin.x = kHorizontalPadding; 330 | textViewFrame.size.width = self.frame.size.width - 2 * kHorizontalPadding; 331 | 332 | if (self.contacts.count == 0){ 333 | lineCount = 0; 334 | textViewFrame.origin.x = kHorizontalPadding; 335 | } 336 | } 337 | textViewFrame.origin.y = lineCount * self.lineHeight + kVerticalPadding + self.viewPadding; 338 | self.textView.frame = textViewFrame; 339 | 340 | // Add text view if it hasn't been added 341 | if (self.textView.superview == nil){ 342 | [self.scrollView addSubview:self.textView]; 343 | } 344 | 345 | // Hide the text view if we are limiting number of selected contacts to 1 and a contact has already been added 346 | if (self.limitToOne && self.contacts.count >= 1){ 347 | self.textView.hidden = YES; 348 | lineCount = 0; 349 | } 350 | 351 | // Adjust scroll view content size 352 | CGRect frame = self.bounds; 353 | CGFloat maxFrameHeight = 2 * self.lineHeight + 2 * self.viewPadding; // limit frame to two lines of content 354 | CGFloat newHeight = (lineCount + 1) * self.lineHeight + 2 * self.viewPadding; 355 | self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width, newHeight); 356 | 357 | // Adjust frame of view if necessary 358 | newHeight = (newHeight > maxFrameHeight) ? maxFrameHeight : newHeight; 359 | if (self.frame.size.height != newHeight){ 360 | // Adjust self height 361 | CGRect selfFrame = self.frame; 362 | selfFrame.size.height = newHeight; 363 | self.frame = selfFrame; 364 | 365 | // Adjust scroll view height 366 | frame.size.height = newHeight; 367 | self.scrollView.frame = frame; 368 | 369 | if ([self.delegate respondsToSelector:@selector(contactPickerDidResize:)]){ 370 | [self.delegate contactPickerDidResize:self]; 371 | } 372 | } 373 | 374 | // Show placeholder if no there are no contacts 375 | if (self.contacts.count == 0){ 376 | self.placeholderLabel.hidden = NO; 377 | } else { 378 | self.placeholderLabel.hidden = YES; 379 | } 380 | } 381 | 382 | #pragma mark - UITextViewDelegate 383 | 384 | - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text; 385 | { 386 | self.textView.hidden = NO; 387 | 388 | if ( [text isEqualToString:@"\n"] ) { // Return key was pressed 389 | return NO; 390 | } 391 | 392 | // Capture "delete" key press when cell is empty 393 | if ([textView.text isEqualToString:@""] && [text isEqualToString:@""]){ 394 | // If no contacts are selected, select the last contact 395 | self.selectedContactBubble = [self.contacts objectForKey:[self.contactKeys lastObject]]; 396 | [self.selectedContactBubble select]; 397 | } 398 | 399 | return YES; 400 | } 401 | 402 | - (void)textViewDidChange:(UITextView *)textView { 403 | if ([self.delegate respondsToSelector:@selector(contactPickerTextViewDidChange:)]){ 404 | [self.delegate contactPickerTextViewDidChange:textView.text]; 405 | } 406 | 407 | if ([textView.text isEqualToString:@""] && self.contacts.count == 0){ 408 | self.placeholderLabel.hidden = NO; 409 | } else { 410 | self.placeholderLabel.hidden = YES; 411 | } 412 | } 413 | 414 | #pragma mark - THContactBubbleDelegate Functions 415 | 416 | - (void)contactBubbleWasSelected:(THContactBubble *)contactBubble { 417 | if (self.selectedContactBubble != nil){ 418 | [self.selectedContactBubble unSelect]; 419 | } 420 | self.selectedContactBubble = contactBubble; 421 | 422 | [self.textView resignFirstResponder]; 423 | self.textView.text = @""; 424 | self.textView.hidden = YES; 425 | } 426 | 427 | - (void)contactBubbleWasUnSelected:(THContactBubble *)contactBubble { 428 | if (self.selectedContactBubble != nil){ 429 | 430 | } 431 | [self.textView becomeFirstResponder]; 432 | self.textView.text = @""; 433 | self.textView.hidden = NO; 434 | } 435 | 436 | - (void)contactBubbleShouldBeRemoved:(THContactBubble *)contactBubble { 437 | [self removeContactBubble:contactBubble]; 438 | } 439 | 440 | #pragma mark - Gesture Recognizer 441 | 442 | - (void)handleTapGesture { 443 | if (self.limitToOne && self.contactKeys.count == 1){ 444 | return; 445 | } 446 | [self scrollToBottomWithAnimation:YES]; 447 | 448 | // Show textField 449 | self.textView.hidden = NO; 450 | [self.textView becomeFirstResponder]; 451 | 452 | // Unselect contact bubble 453 | [self.selectedContactBubble unSelect]; 454 | self.selectedContactBubble = nil; 455 | } 456 | 457 | #pragma mark - UIScrollViewDelegate 458 | 459 | - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { 460 | if (_shouldSelectTextView){ 461 | _shouldSelectTextView = NO; 462 | [self selectTextView]; 463 | } 464 | } 465 | 466 | /* 467 | // Only override drawRect: if you perform custom drawing. 468 | // An empty implementation adversely affects performance during animation. 469 | - (void)drawRect:(CGRect)rect 470 | { 471 | // Drawing code 472 | } 473 | */ 474 | 475 | @end 476 | -------------------------------------------------------------------------------- /ContactPicker/THContactPickerViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ContactPickerViewController.m 3 | // ContactPicker 4 | // 5 | // Created by Tristan Himmelman on 11/2/12. 6 | // Copyright (c) 2012 Tristan Himmelman. All rights reserved. 7 | // 8 | 9 | #import "THContactPickerViewController.h" 10 | #import 11 | #import "THContact.h" 12 | 13 | UIBarButtonItem *barButton; 14 | 15 | @interface THContactPickerViewController () 16 | 17 | @property (nonatomic, assign) ABAddressBookRef addressBookRef; 18 | 19 | @end 20 | 21 | //#define kKeyboardHeight 216.0 22 | #define kKeyboardHeight 0.0 23 | 24 | @implementation THContactPickerViewController 25 | 26 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 27 | { 28 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 29 | if (self) { 30 | self.title = @"Select Contacts (0)"; 31 | 32 | CFErrorRef error; 33 | _addressBookRef = ABAddressBookCreateWithOptions(NULL, &error); 34 | } 35 | return self; 36 | } 37 | 38 | - (void)viewDidLoad 39 | { 40 | [super viewDidLoad]; 41 | // Do any additional setup after loading the view from its nib. 42 | // UIBarButtonItem * barButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonItemStyleBordered target:self action:@selector(removeAllContacts:)]; 43 | 44 | barButton = [[UIBarButtonItem alloc] initWithTitle:@"Done" style:UIBarButtonItemStyleDone target:self action:@selector(done:)]; 45 | barButton.enabled = FALSE; 46 | 47 | self.navigationItem.rightBarButtonItem = barButton; 48 | 49 | // Initialize and add Contact Picker View 50 | self.contactPickerView = [[THContactPickerView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 100)]; 51 | self.contactPickerView.delegate = self; 52 | [self.contactPickerView setPlaceholderString:@"Type contact name"]; 53 | [self.view addSubview:self.contactPickerView]; 54 | 55 | // Fill the rest of the view with the table view 56 | self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, self.contactPickerView.frame.size.height, self.view.frame.size.width, self.view.frame.size.height - self.contactPickerView.frame.size.height - kKeyboardHeight) style:UITableViewStylePlain]; 57 | self.tableView.delegate = self; 58 | self.tableView.dataSource = self; 59 | 60 | [self.tableView registerNib:[UINib nibWithNibName:@"THContactPickerTableViewCell" bundle:nil] forCellReuseIdentifier:@"ContactCell"]; 61 | 62 | [self.view insertSubview:self.tableView belowSubview:self.contactPickerView]; 63 | 64 | ABAddressBookRequestAccessWithCompletion(self.addressBookRef, ^(bool granted, CFErrorRef error) { 65 | if (granted) { 66 | dispatch_async(dispatch_get_main_queue(), ^{ 67 | [self getContactsFromAddressBook]; 68 | }); 69 | } else { 70 | // TODO: Show alert 71 | } 72 | }); 73 | } 74 | 75 | -(void)getContactsFromAddressBook 76 | { 77 | CFErrorRef error = NULL; 78 | self.contacts = [[NSMutableArray alloc]init]; 79 | 80 | ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, &error); 81 | if (addressBook) { 82 | NSArray *allContacts = (__bridge_transfer NSArray*)ABAddressBookCopyArrayOfAllPeople(addressBook); 83 | NSMutableArray *mutableContacts = [NSMutableArray arrayWithCapacity:allContacts.count]; 84 | 85 | NSUInteger i = 0; 86 | for (i = 0; i<[allContacts count]; i++) 87 | { 88 | THContact *contact = [[THContact alloc] init]; 89 | ABRecordRef contactPerson = (__bridge ABRecordRef)allContacts[i]; 90 | contact.recordId = ABRecordGetRecordID(contactPerson); 91 | 92 | // Get first and last names 93 | NSString *firstName = (__bridge_transfer NSString*)ABRecordCopyValue(contactPerson, kABPersonFirstNameProperty); 94 | NSString *lastName = (__bridge_transfer NSString*)ABRecordCopyValue(contactPerson, kABPersonLastNameProperty); 95 | 96 | // Set Contact properties 97 | contact.firstName = firstName; 98 | contact.lastName = lastName; 99 | 100 | // Get mobile number 101 | ABMultiValueRef phonesRef = ABRecordCopyValue(contactPerson, kABPersonPhoneProperty); 102 | contact.phone = [self getMobilePhoneProperty:phonesRef]; 103 | if(phonesRef) { 104 | CFRelease(phonesRef); 105 | } 106 | 107 | // Get image if it exists 108 | NSData *imgData = (__bridge_transfer NSData *)ABPersonCopyImageData(contactPerson); 109 | contact.image = [UIImage imageWithData:imgData]; 110 | if (!contact.image) { 111 | contact.image = [UIImage imageNamed:@"icon-avatar-60x60"]; 112 | } 113 | 114 | [mutableContacts addObject:contact]; 115 | } 116 | 117 | if(addressBook) { 118 | CFRelease(addressBook); 119 | } 120 | 121 | self.contacts = [NSArray arrayWithArray:mutableContacts]; 122 | self.selectedContacts = [NSMutableArray array]; 123 | self.filteredContacts = self.contacts; 124 | 125 | [self.tableView reloadData]; 126 | } 127 | else 128 | { 129 | NSLog(@"Error"); 130 | 131 | } 132 | } 133 | 134 | - (void) refreshContacts 135 | { 136 | for (THContact* contact in self.contacts) 137 | { 138 | [self refreshContact: contact]; 139 | } 140 | [self.tableView reloadData]; 141 | } 142 | 143 | - (void) refreshContact:(THContact*)contact 144 | { 145 | 146 | ABRecordRef contactPerson = ABAddressBookGetPersonWithRecordID(self.addressBookRef, (ABRecordID)contact.recordId); 147 | contact.recordId = ABRecordGetRecordID(contactPerson); 148 | 149 | // Get first and last names 150 | NSString *firstName = (__bridge_transfer NSString*)ABRecordCopyValue(contactPerson, kABPersonFirstNameProperty); 151 | NSString *lastName = (__bridge_transfer NSString*)ABRecordCopyValue(contactPerson, kABPersonLastNameProperty); 152 | 153 | // Set Contact properties 154 | contact.firstName = firstName; 155 | contact.lastName = lastName; 156 | 157 | // Get mobile number 158 | ABMultiValueRef phonesRef = ABRecordCopyValue(contactPerson, kABPersonPhoneProperty); 159 | contact.phone = [self getMobilePhoneProperty:phonesRef]; 160 | if(phonesRef) { 161 | CFRelease(phonesRef); 162 | } 163 | 164 | // Get image if it exists 165 | NSData *imgData = (__bridge_transfer NSData *)ABPersonCopyImageData(contactPerson); 166 | contact.image = [UIImage imageWithData:imgData]; 167 | if (!contact.image) { 168 | contact.image = [UIImage imageNamed:@"icon-avatar-60x60"]; 169 | } 170 | } 171 | 172 | - (NSString *)getMobilePhoneProperty:(ABMultiValueRef)phonesRef 173 | { 174 | for (int i=0; i < ABMultiValueGetCount(phonesRef); i++) { 175 | CFStringRef currentPhoneLabel = ABMultiValueCopyLabelAtIndex(phonesRef, i); 176 | CFStringRef currentPhoneValue = ABMultiValueCopyValueAtIndex(phonesRef, i); 177 | 178 | if(currentPhoneLabel) { 179 | if (CFStringCompare(currentPhoneLabel, kABPersonPhoneMobileLabel, 0) == kCFCompareEqualTo) { 180 | return (__bridge NSString *)currentPhoneValue; 181 | } 182 | 183 | if (CFStringCompare(currentPhoneLabel, kABHomeLabel, 0) == kCFCompareEqualTo) { 184 | return (__bridge NSString *)currentPhoneValue; 185 | } 186 | } 187 | if(currentPhoneLabel) { 188 | CFRelease(currentPhoneLabel); 189 | } 190 | if(currentPhoneValue) { 191 | CFRelease(currentPhoneValue); 192 | } 193 | } 194 | 195 | return nil; 196 | } 197 | 198 | - (void)viewWillAppear:(BOOL)animated { 199 | [super viewWillAppear:animated]; 200 | dispatch_async(dispatch_get_main_queue(), ^{ 201 | [self refreshContacts]; 202 | }); 203 | } 204 | 205 | - (void)viewDidLayoutSubviews { 206 | [super viewDidLayoutSubviews]; 207 | CGFloat topOffset = 0; 208 | if ([self respondsToSelector:@selector(topLayoutGuide)]){ 209 | topOffset = self.topLayoutGuide.length; 210 | } 211 | CGRect frame = self.contactPickerView.frame; 212 | frame.origin.y = topOffset; 213 | self.contactPickerView.frame = frame; 214 | [self adjustTableViewFrame:NO]; 215 | } 216 | 217 | - (void)didReceiveMemoryWarning 218 | { 219 | [super didReceiveMemoryWarning]; 220 | // Dispose of any resources that can be recreated. 221 | } 222 | 223 | - (void)adjustTableViewFrame:(BOOL)animated { 224 | CGRect frame = self.tableView.frame; 225 | // This places the table view right under the text field 226 | frame.origin.y = self.contactPickerView.frame.size.height; 227 | // Calculate the remaining distance 228 | frame.size.height = self.view.frame.size.height - self.contactPickerView.frame.size.height - kKeyboardHeight; 229 | 230 | if(animated) { 231 | [UIView beginAnimations:nil context:nil]; 232 | [UIView setAnimationDuration:0.3]; 233 | [UIView setAnimationDelay:0.1]; 234 | [UIView setAnimationCurve:UIViewAnimationCurveEaseOut]; 235 | 236 | self.tableView.frame = frame; 237 | 238 | [UIView commitAnimations]; 239 | } 240 | else{ 241 | self.tableView.frame = frame; 242 | } 243 | } 244 | 245 | 246 | 247 | #pragma mark - UITableView Delegate and Datasource functions 248 | 249 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 250 | return 1; 251 | } 252 | 253 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 254 | return self.filteredContacts.count; 255 | } 256 | 257 | - (CGFloat)tableView: (UITableView*)tableView heightForRowAtIndexPath: (NSIndexPath*) indexPath { 258 | return 70; 259 | } 260 | 261 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 262 | 263 | // Get the desired contact from the filteredContacts array 264 | THContact *contact = [self.filteredContacts objectAtIndex:indexPath.row]; 265 | 266 | // Initialize the table view cell 267 | NSString *cellIdentifier = @"ContactCell"; 268 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; 269 | if (cell == nil){ 270 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; 271 | } 272 | 273 | // Get the UI elements in the cell; 274 | UILabel *contactNameLabel = (UILabel *)[cell viewWithTag:101]; 275 | UILabel *mobilePhoneNumberLabel = (UILabel *)[cell viewWithTag:102]; 276 | UIImageView *contactImage = (UIImageView *)[cell viewWithTag:103]; 277 | UIImageView *checkboxImageView = (UIImageView *)[cell viewWithTag:104]; 278 | 279 | // Assign values to to US elements 280 | contactNameLabel.text = [contact fullName]; 281 | mobilePhoneNumberLabel.text = contact.phone; 282 | if(contact.image) { 283 | contactImage.image = contact.image; 284 | } 285 | contactImage.layer.masksToBounds = YES; 286 | contactImage.layer.cornerRadius = 20; 287 | 288 | // Set the checked state for the contact selection checkbox 289 | UIImage *image; 290 | if ([self.selectedContacts containsObject:[self.filteredContacts objectAtIndex:indexPath.row]]){ 291 | //cell.accessoryType = UITableViewCellAccessoryCheckmark; 292 | image = [UIImage imageNamed:@"icon-checkbox-selected-green-25x25"]; 293 | } else { 294 | //cell.accessoryType = UITableViewCellAccessoryNone; 295 | image = [UIImage imageNamed:@"icon-checkbox-unselected-25x25"]; 296 | } 297 | checkboxImageView.image = image; 298 | 299 | // Assign a UIButton to the accessoryView cell property 300 | cell.accessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure]; 301 | // Set a target and selector for the accessoryView UIControlEventTouchUpInside 302 | [(UIButton *)cell.accessoryView addTarget:self action:@selector(viewContactDetail:) forControlEvents:UIControlEventTouchUpInside]; 303 | cell.accessoryView.tag = contact.recordId; //so we know which ABRecord in the IBAction method 304 | 305 | // // For custom accessory view button use this. 306 | // UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem]; 307 | // button.frame = CGRectMake(0.0f, 0.0f, 150.0f, 25.0f); 308 | // 309 | // [button setTitle:@"Expand" 310 | // forState:UIControlStateNormal]; 311 | // 312 | // [button addTarget:self 313 | // action:@selector(viewContactDetail:) 314 | // forControlEvents:UIControlEventTouchUpInside]; 315 | // 316 | // cell.accessoryView = button; 317 | 318 | return cell; 319 | } 320 | 321 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 322 | // Hide Keyboard 323 | [self.contactPickerView resignKeyboard]; 324 | 325 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 326 | 327 | UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; 328 | 329 | // This uses the custom cellView 330 | // Set the custom imageView 331 | THContact *user = [self.filteredContacts objectAtIndex:indexPath.row]; 332 | UIImageView *checkboxImageView = (UIImageView *)[cell viewWithTag:104]; 333 | UIImage *image; 334 | 335 | if ([self.selectedContacts containsObject:user]){ // contact is already selected so remove it from ContactPickerView 336 | //cell.accessoryType = UITableViewCellAccessoryNone; 337 | [self.selectedContacts removeObject:user]; 338 | [self.contactPickerView removeContact:user]; 339 | // Set checkbox to "unselected" 340 | image = [UIImage imageNamed:@"icon-checkbox-unselected-25x25"]; 341 | } else { 342 | // Contact has not been selected, add it to THContactPickerView 343 | //cell.accessoryType = UITableViewCellAccessoryCheckmark; 344 | [self.selectedContacts addObject:user]; 345 | [self.contactPickerView addContact:user withName:user.fullName]; 346 | // Set checkbox to "selected" 347 | image = [UIImage imageNamed:@"icon-checkbox-selected-green-25x25"]; 348 | } 349 | 350 | // Enable Done button if total selected contacts > 0 351 | if(self.selectedContacts.count > 0) { 352 | barButton.enabled = TRUE; 353 | } 354 | else 355 | { 356 | barButton.enabled = FALSE; 357 | } 358 | 359 | // Update window title 360 | self.title = [NSString stringWithFormat:@"Add Members (%lu)", (unsigned long)self.selectedContacts.count]; 361 | 362 | // Set checkbox image 363 | checkboxImageView.image = image; 364 | // Reset the filtered contacts 365 | self.filteredContacts = self.contacts; 366 | // Refresh the tableview 367 | [self.tableView reloadData]; 368 | } 369 | 370 | #pragma mark - THContactPickerTextViewDelegate 371 | 372 | - (void)contactPickerTextViewDidChange:(NSString *)textViewText { 373 | if ([textViewText isEqualToString:@""]){ 374 | self.filteredContacts = self.contacts; 375 | } else { 376 | NSPredicate *predicate = [NSPredicate predicateWithFormat:@"self.%@ contains[cd] %@ OR self.%@ contains[cd] %@", @"firstName", textViewText, @"lastName", textViewText]; 377 | self.filteredContacts = [self.contacts filteredArrayUsingPredicate:predicate]; 378 | } 379 | [self.tableView reloadData]; 380 | } 381 | 382 | - (void)contactPickerDidResize:(THContactPickerView *)contactPickerView { 383 | [self adjustTableViewFrame:YES]; 384 | } 385 | 386 | - (void)contactPickerDidRemoveContact:(id)contact { 387 | [self.selectedContacts removeObject:contact]; 388 | 389 | NSUInteger index = [self.contacts indexOfObject:contact]; 390 | UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:index inSection:0]]; 391 | //cell.accessoryType = UITableViewCellAccessoryNone; 392 | 393 | // Enable Done button if total selected contacts > 0 394 | if(self.selectedContacts.count > 0) { 395 | barButton.enabled = TRUE; 396 | } 397 | else 398 | { 399 | barButton.enabled = FALSE; 400 | } 401 | 402 | // Set unchecked image 403 | UIImageView *checkboxImageView = (UIImageView *)[cell viewWithTag:104]; 404 | UIImage *image; 405 | image = [UIImage imageNamed:@"icon-checkbox-unselected-25x25"]; 406 | checkboxImageView.image = image; 407 | 408 | // Update window title 409 | self.title = [NSString stringWithFormat:@"Add Members (%lu)", (unsigned long)self.selectedContacts.count]; 410 | } 411 | 412 | - (void)removeAllContacts:(id)sender 413 | { 414 | [self.contactPickerView removeAllContacts]; 415 | [self.selectedContacts removeAllObjects]; 416 | self.filteredContacts = self.contacts; 417 | [self.tableView reloadData]; 418 | } 419 | #pragma mark ABPersonViewControllerDelegate 420 | 421 | - (BOOL)personViewController:(ABPersonViewController *)personViewController shouldPerformDefaultActionForPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier 422 | { 423 | return YES; 424 | } 425 | 426 | 427 | // This opens the apple contact details view: ABPersonViewController 428 | //TODO: make a THContactPickerDetailViewController 429 | - (IBAction)viewContactDetail:(UIButton*)sender { 430 | ABRecordID personId = (ABRecordID)sender.tag; 431 | ABPersonViewController *view = [[ABPersonViewController alloc] init]; 432 | view.addressBook = self.addressBookRef; 433 | view.personViewDelegate = self; 434 | view.displayedPerson = ABAddressBookGetPersonWithRecordID(self.addressBookRef, personId); 435 | 436 | 437 | [self.navigationController pushViewController:view animated:YES]; 438 | } 439 | 440 | // TODO: send contact object 441 | - (void)done:(id)sender 442 | { 443 | UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Done!" 444 | message:@"Now do whatevet you want!" 445 | delegate:nil 446 | cancelButtonTitle:@"Ok" 447 | otherButtonTitles:nil]; 448 | [alertView show]; 449 | } 450 | 451 | @end 452 | -------------------------------------------------------------------------------- /ContactPicker.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0446053018E4D5750091723F /* THContactPickerTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 0446052E18E4D5750091723F /* THContactPickerTableViewCell.m */; }; 11 | 0446053118E4D5750091723F /* THContactPickerTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0446052F18E4D5750091723F /* THContactPickerTableViewCell.xib */; }; 12 | 0446053418E4D5D50091723F /* THContact.m in Sources */ = {isa = PBXBuildFile; fileRef = 0446053318E4D5D50091723F /* THContact.m */; }; 13 | 0446053618E4D6180091723F /* AddressBook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0446053518E4D6180091723F /* AddressBook.framework */; }; 14 | 0446053D18E4D6990091723F /* icon-avatar-60x60.png in Resources */ = {isa = PBXBuildFile; fileRef = 0446053718E4D6990091723F /* icon-avatar-60x60.png */; }; 15 | 0446053E18E4D6990091723F /* icon-avatar-60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0446053818E4D6990091723F /* icon-avatar-60x60@2x.png */; }; 16 | 0446053F18E4D6990091723F /* icon-checkbox-selected-green-25x25.png in Resources */ = {isa = PBXBuildFile; fileRef = 0446053918E4D6990091723F /* icon-checkbox-selected-green-25x25.png */; }; 17 | 0446054018E4D6990091723F /* icon-checkbox-selected-green-25x25@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0446053A18E4D6990091723F /* icon-checkbox-selected-green-25x25@2x.png */; }; 18 | 0446054118E4D6990091723F /* icon-checkbox-unselected-25x25.png in Resources */ = {isa = PBXBuildFile; fileRef = 0446053B18E4D6990091723F /* icon-checkbox-unselected-25x25.png */; }; 19 | 0446054218E4D6990091723F /* icon-checkbox-unselected-25x25@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0446053C18E4D6990091723F /* icon-checkbox-unselected-25x25@2x.png */; }; 20 | 29A71BE81644824F005286F1 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29A71BE71644824F005286F1 /* UIKit.framework */; }; 21 | 29A71BEA1644824F005286F1 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29A71BE91644824F005286F1 /* Foundation.framework */; }; 22 | 29A71BEC1644824F005286F1 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29A71BEB1644824F005286F1 /* CoreGraphics.framework */; }; 23 | 29A71BF216448250005286F1 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 29A71BF016448250005286F1 /* InfoPlist.strings */; }; 24 | 29A71BF416448250005286F1 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29A71BF316448250005286F1 /* main.m */; }; 25 | 29A71BF816448250005286F1 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 29A71BF716448250005286F1 /* AppDelegate.m */; }; 26 | 29A71BFA16448250005286F1 /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = 29A71BF916448250005286F1 /* Default.png */; }; 27 | 29A71BFC16448250005286F1 /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29A71BFB16448250005286F1 /* Default@2x.png */; }; 28 | 29A71BFE16448250005286F1 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 29A71BFD16448250005286F1 /* Default-568h@2x.png */; }; 29 | 29A71C11164482BB005286F1 /* THContactPickerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 29A71C0F164482BB005286F1 /* THContactPickerViewController.m */; }; 30 | 29A71C12164482BB005286F1 /* THContactPickerViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 29A71C10164482BB005286F1 /* THContactPickerViewController.xib */; }; 31 | 29A71C151644834B005286F1 /* THContactPickerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 29A71C141644834B005286F1 /* THContactPickerView.m */; }; 32 | 29A71C1816448473005286F1 /* THContactBubble.m in Sources */ = {isa = PBXBuildFile; fileRef = 29A71C1716448473005286F1 /* THContactBubble.m */; }; 33 | 4F743B831904207300AE8765 /* AddressBookUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F743B821904207300AE8765 /* AddressBookUI.framework */; }; 34 | 6AE31FCC1644DB2C00505230 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6AE31FCB1644DB2C00505230 /* QuartzCore.framework */; }; 35 | 84F796481670C79500A8965D /* THBubbleColor.m in Sources */ = {isa = PBXBuildFile; fileRef = 84F796471670C79500A8965D /* THBubbleColor.m */; }; 36 | /* End PBXBuildFile section */ 37 | 38 | /* Begin PBXFileReference section */ 39 | 0446052D18E4D5750091723F /* THContactPickerTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = THContactPickerTableViewCell.h; sourceTree = ""; }; 40 | 0446052E18E4D5750091723F /* THContactPickerTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = THContactPickerTableViewCell.m; sourceTree = ""; }; 41 | 0446052F18E4D5750091723F /* THContactPickerTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = THContactPickerTableViewCell.xib; sourceTree = ""; }; 42 | 0446053218E4D5D50091723F /* THContact.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = THContact.h; sourceTree = ""; }; 43 | 0446053318E4D5D50091723F /* THContact.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = THContact.m; sourceTree = ""; }; 44 | 0446053518E4D6180091723F /* AddressBook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBook.framework; path = System/Library/Frameworks/AddressBook.framework; sourceTree = SDKROOT; }; 45 | 0446053718E4D6990091723F /* icon-avatar-60x60.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-avatar-60x60.png"; sourceTree = ""; }; 46 | 0446053818E4D6990091723F /* icon-avatar-60x60@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-avatar-60x60@2x.png"; sourceTree = ""; }; 47 | 0446053918E4D6990091723F /* icon-checkbox-selected-green-25x25.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-checkbox-selected-green-25x25.png"; sourceTree = ""; }; 48 | 0446053A18E4D6990091723F /* icon-checkbox-selected-green-25x25@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-checkbox-selected-green-25x25@2x.png"; sourceTree = ""; }; 49 | 0446053B18E4D6990091723F /* icon-checkbox-unselected-25x25.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-checkbox-unselected-25x25.png"; sourceTree = ""; }; 50 | 0446053C18E4D6990091723F /* icon-checkbox-unselected-25x25@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-checkbox-unselected-25x25@2x.png"; sourceTree = ""; }; 51 | 29A71BE31644824F005286F1 /* ContactPicker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ContactPicker.app; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | 29A71BE71644824F005286F1 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 53 | 29A71BE91644824F005286F1 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 54 | 29A71BEB1644824F005286F1 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 55 | 29A71BEF1644824F005286F1 /* ContactPicker-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "ContactPicker-Info.plist"; sourceTree = ""; }; 56 | 29A71BF116448250005286F1 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 57 | 29A71BF316448250005286F1 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 58 | 29A71BF516448250005286F1 /* ContactPicker-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ContactPicker-Prefix.pch"; sourceTree = ""; }; 59 | 29A71BF616448250005286F1 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 60 | 29A71BF716448250005286F1 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 61 | 29A71BF916448250005286F1 /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Default.png; sourceTree = ""; }; 62 | 29A71BFB16448250005286F1 /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default@2x.png"; sourceTree = ""; }; 63 | 29A71BFD16448250005286F1 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; 64 | 29A71C0E164482BB005286F1 /* THContactPickerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = THContactPickerViewController.h; sourceTree = ""; }; 65 | 29A71C0F164482BB005286F1 /* THContactPickerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = THContactPickerViewController.m; sourceTree = ""; }; 66 | 29A71C10164482BB005286F1 /* THContactPickerViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = THContactPickerViewController.xib; sourceTree = ""; }; 67 | 29A71C131644834B005286F1 /* THContactPickerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = THContactPickerView.h; sourceTree = ""; }; 68 | 29A71C141644834B005286F1 /* THContactPickerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = THContactPickerView.m; sourceTree = ""; }; 69 | 29A71C1616448473005286F1 /* THContactBubble.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = THContactBubble.h; sourceTree = ""; }; 70 | 29A71C1716448473005286F1 /* THContactBubble.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = THContactBubble.m; sourceTree = ""; }; 71 | 4F743B821904207300AE8765 /* AddressBookUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBookUI.framework; path = System/Library/Frameworks/AddressBookUI.framework; sourceTree = SDKROOT; }; 72 | 6AE31FCB1644DB2C00505230 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 73 | 84F796461670C79500A8965D /* THBubbleColor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = THBubbleColor.h; sourceTree = ""; }; 74 | 84F796471670C79500A8965D /* THBubbleColor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = THBubbleColor.m; sourceTree = ""; }; 75 | /* End PBXFileReference section */ 76 | 77 | /* Begin PBXFrameworksBuildPhase section */ 78 | 29A71BE01644824F005286F1 /* Frameworks */ = { 79 | isa = PBXFrameworksBuildPhase; 80 | buildActionMask = 2147483647; 81 | files = ( 82 | 4F743B831904207300AE8765 /* AddressBookUI.framework in Frameworks */, 83 | 0446053618E4D6180091723F /* AddressBook.framework in Frameworks */, 84 | 6AE31FCC1644DB2C00505230 /* QuartzCore.framework in Frameworks */, 85 | 29A71BE81644824F005286F1 /* UIKit.framework in Frameworks */, 86 | 29A71BEA1644824F005286F1 /* Foundation.framework in Frameworks */, 87 | 29A71BEC1644824F005286F1 /* CoreGraphics.framework in Frameworks */, 88 | ); 89 | runOnlyForDeploymentPostprocessing = 0; 90 | }; 91 | /* End PBXFrameworksBuildPhase section */ 92 | 93 | /* Begin PBXGroup section */ 94 | 29A71BD81644824F005286F1 = { 95 | isa = PBXGroup; 96 | children = ( 97 | 29A71BED1644824F005286F1 /* ContactPicker */, 98 | 29A71BE61644824F005286F1 /* Frameworks */, 99 | 29A71BE41644824F005286F1 /* Products */, 100 | ); 101 | sourceTree = ""; 102 | }; 103 | 29A71BE41644824F005286F1 /* Products */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | 29A71BE31644824F005286F1 /* ContactPicker.app */, 107 | ); 108 | name = Products; 109 | sourceTree = ""; 110 | }; 111 | 29A71BE61644824F005286F1 /* Frameworks */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 4F743B821904207300AE8765 /* AddressBookUI.framework */, 115 | 0446053518E4D6180091723F /* AddressBook.framework */, 116 | 6AE31FCB1644DB2C00505230 /* QuartzCore.framework */, 117 | 29A71BE71644824F005286F1 /* UIKit.framework */, 118 | 29A71BE91644824F005286F1 /* Foundation.framework */, 119 | 29A71BEB1644824F005286F1 /* CoreGraphics.framework */, 120 | ); 121 | name = Frameworks; 122 | sourceTree = ""; 123 | }; 124 | 29A71BED1644824F005286F1 /* ContactPicker */ = { 125 | isa = PBXGroup; 126 | children = ( 127 | 29A71BF616448250005286F1 /* AppDelegate.h */, 128 | 29A71BF716448250005286F1 /* AppDelegate.m */, 129 | 29A71C1616448473005286F1 /* THContactBubble.h */, 130 | 29A71C1716448473005286F1 /* THContactBubble.m */, 131 | 29A71C131644834B005286F1 /* THContactPickerView.h */, 132 | 29A71C141644834B005286F1 /* THContactPickerView.m */, 133 | 29A71BEE1644824F005286F1 /* Supporting Files */, 134 | 29A71C0E164482BB005286F1 /* THContactPickerViewController.h */, 135 | 29A71C0F164482BB005286F1 /* THContactPickerViewController.m */, 136 | 29A71C10164482BB005286F1 /* THContactPickerViewController.xib */, 137 | 84F796461670C79500A8965D /* THBubbleColor.h */, 138 | 84F796471670C79500A8965D /* THBubbleColor.m */, 139 | 0446052D18E4D5750091723F /* THContactPickerTableViewCell.h */, 140 | 0446052E18E4D5750091723F /* THContactPickerTableViewCell.m */, 141 | 0446052F18E4D5750091723F /* THContactPickerTableViewCell.xib */, 142 | 0446053218E4D5D50091723F /* THContact.h */, 143 | 0446053318E4D5D50091723F /* THContact.m */, 144 | ); 145 | path = ContactPicker; 146 | sourceTree = ""; 147 | }; 148 | 29A71BEE1644824F005286F1 /* Supporting Files */ = { 149 | isa = PBXGroup; 150 | children = ( 151 | 29A71BEF1644824F005286F1 /* ContactPicker-Info.plist */, 152 | 29A71BF016448250005286F1 /* InfoPlist.strings */, 153 | 29A71BF316448250005286F1 /* main.m */, 154 | 0446053718E4D6990091723F /* icon-avatar-60x60.png */, 155 | 0446053818E4D6990091723F /* icon-avatar-60x60@2x.png */, 156 | 0446053918E4D6990091723F /* icon-checkbox-selected-green-25x25.png */, 157 | 0446053A18E4D6990091723F /* icon-checkbox-selected-green-25x25@2x.png */, 158 | 0446053B18E4D6990091723F /* icon-checkbox-unselected-25x25.png */, 159 | 0446053C18E4D6990091723F /* icon-checkbox-unselected-25x25@2x.png */, 160 | 29A71BF516448250005286F1 /* ContactPicker-Prefix.pch */, 161 | 29A71BF916448250005286F1 /* Default.png */, 162 | 29A71BFB16448250005286F1 /* Default@2x.png */, 163 | 29A71BFD16448250005286F1 /* Default-568h@2x.png */, 164 | ); 165 | name = "Supporting Files"; 166 | sourceTree = ""; 167 | }; 168 | /* End PBXGroup section */ 169 | 170 | /* Begin PBXNativeTarget section */ 171 | 29A71BE21644824F005286F1 /* ContactPicker */ = { 172 | isa = PBXNativeTarget; 173 | buildConfigurationList = 29A71C0116448250005286F1 /* Build configuration list for PBXNativeTarget "ContactPicker" */; 174 | buildPhases = ( 175 | 29A71BDF1644824F005286F1 /* Sources */, 176 | 29A71BE01644824F005286F1 /* Frameworks */, 177 | 29A71BE11644824F005286F1 /* Resources */, 178 | ); 179 | buildRules = ( 180 | ); 181 | dependencies = ( 182 | ); 183 | name = ContactPicker; 184 | productName = ContactPicker; 185 | productReference = 29A71BE31644824F005286F1 /* ContactPicker.app */; 186 | productType = "com.apple.product-type.application"; 187 | }; 188 | /* End PBXNativeTarget section */ 189 | 190 | /* Begin PBXProject section */ 191 | 29A71BDA1644824F005286F1 /* Project object */ = { 192 | isa = PBXProject; 193 | attributes = { 194 | LastUpgradeCheck = 0450; 195 | ORGANIZATIONNAME = "Tristan Himmelman"; 196 | }; 197 | buildConfigurationList = 29A71BDD1644824F005286F1 /* Build configuration list for PBXProject "ContactPicker" */; 198 | compatibilityVersion = "Xcode 3.2"; 199 | developmentRegion = English; 200 | hasScannedForEncodings = 0; 201 | knownRegions = ( 202 | en, 203 | ); 204 | mainGroup = 29A71BD81644824F005286F1; 205 | productRefGroup = 29A71BE41644824F005286F1 /* Products */; 206 | projectDirPath = ""; 207 | projectRoot = ""; 208 | targets = ( 209 | 29A71BE21644824F005286F1 /* ContactPicker */, 210 | ); 211 | }; 212 | /* End PBXProject section */ 213 | 214 | /* Begin PBXResourcesBuildPhase section */ 215 | 29A71BE11644824F005286F1 /* Resources */ = { 216 | isa = PBXResourcesBuildPhase; 217 | buildActionMask = 2147483647; 218 | files = ( 219 | 0446054018E4D6990091723F /* icon-checkbox-selected-green-25x25@2x.png in Resources */, 220 | 29A71BF216448250005286F1 /* InfoPlist.strings in Resources */, 221 | 0446054118E4D6990091723F /* icon-checkbox-unselected-25x25.png in Resources */, 222 | 0446053D18E4D6990091723F /* icon-avatar-60x60.png in Resources */, 223 | 29A71BFA16448250005286F1 /* Default.png in Resources */, 224 | 0446053F18E4D6990091723F /* icon-checkbox-selected-green-25x25.png in Resources */, 225 | 0446053E18E4D6990091723F /* icon-avatar-60x60@2x.png in Resources */, 226 | 29A71BFC16448250005286F1 /* Default@2x.png in Resources */, 227 | 29A71BFE16448250005286F1 /* Default-568h@2x.png in Resources */, 228 | 0446053118E4D5750091723F /* THContactPickerTableViewCell.xib in Resources */, 229 | 29A71C12164482BB005286F1 /* THContactPickerViewController.xib in Resources */, 230 | 0446054218E4D6990091723F /* icon-checkbox-unselected-25x25@2x.png in Resources */, 231 | ); 232 | runOnlyForDeploymentPostprocessing = 0; 233 | }; 234 | /* End PBXResourcesBuildPhase section */ 235 | 236 | /* Begin PBXSourcesBuildPhase section */ 237 | 29A71BDF1644824F005286F1 /* Sources */ = { 238 | isa = PBXSourcesBuildPhase; 239 | buildActionMask = 2147483647; 240 | files = ( 241 | 29A71BF416448250005286F1 /* main.m in Sources */, 242 | 29A71BF816448250005286F1 /* AppDelegate.m in Sources */, 243 | 0446053018E4D5750091723F /* THContactPickerTableViewCell.m in Sources */, 244 | 29A71C11164482BB005286F1 /* THContactPickerViewController.m in Sources */, 245 | 29A71C151644834B005286F1 /* THContactPickerView.m in Sources */, 246 | 29A71C1816448473005286F1 /* THContactBubble.m in Sources */, 247 | 0446053418E4D5D50091723F /* THContact.m in Sources */, 248 | 84F796481670C79500A8965D /* THBubbleColor.m in Sources */, 249 | ); 250 | runOnlyForDeploymentPostprocessing = 0; 251 | }; 252 | /* End PBXSourcesBuildPhase section */ 253 | 254 | /* Begin PBXVariantGroup section */ 255 | 29A71BF016448250005286F1 /* InfoPlist.strings */ = { 256 | isa = PBXVariantGroup; 257 | children = ( 258 | 29A71BF116448250005286F1 /* en */, 259 | ); 260 | name = InfoPlist.strings; 261 | sourceTree = ""; 262 | }; 263 | /* End PBXVariantGroup section */ 264 | 265 | /* Begin XCBuildConfiguration section */ 266 | 29A71BFF16448250005286F1 /* Debug */ = { 267 | isa = XCBuildConfiguration; 268 | buildSettings = { 269 | ALWAYS_SEARCH_USER_PATHS = NO; 270 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 271 | CLANG_CXX_LIBRARY = "libc++"; 272 | CLANG_ENABLE_OBJC_ARC = YES; 273 | CLANG_WARN_EMPTY_BODY = YES; 274 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 275 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 276 | COPY_PHASE_STRIP = NO; 277 | GCC_C_LANGUAGE_STANDARD = gnu99; 278 | GCC_DYNAMIC_NO_PIC = NO; 279 | GCC_OPTIMIZATION_LEVEL = 0; 280 | GCC_PREPROCESSOR_DEFINITIONS = ( 281 | "DEBUG=1", 282 | "$(inherited)", 283 | ); 284 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 285 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 286 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 287 | GCC_WARN_UNUSED_VARIABLE = YES; 288 | IPHONEOS_DEPLOYMENT_TARGET = 6.0; 289 | SDKROOT = iphoneos; 290 | TARGETED_DEVICE_FAMILY = "1,2"; 291 | }; 292 | name = Debug; 293 | }; 294 | 29A71C0016448250005286F1 /* Release */ = { 295 | isa = XCBuildConfiguration; 296 | buildSettings = { 297 | ALWAYS_SEARCH_USER_PATHS = NO; 298 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 299 | CLANG_CXX_LIBRARY = "libc++"; 300 | CLANG_ENABLE_OBJC_ARC = YES; 301 | CLANG_WARN_EMPTY_BODY = YES; 302 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 303 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 304 | COPY_PHASE_STRIP = YES; 305 | GCC_C_LANGUAGE_STANDARD = gnu99; 306 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 307 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 308 | GCC_WARN_UNUSED_VARIABLE = YES; 309 | IPHONEOS_DEPLOYMENT_TARGET = 6.0; 310 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 311 | SDKROOT = iphoneos; 312 | TARGETED_DEVICE_FAMILY = "1,2"; 313 | VALIDATE_PRODUCT = YES; 314 | }; 315 | name = Release; 316 | }; 317 | 29A71C0216448250005286F1 /* Debug */ = { 318 | isa = XCBuildConfiguration; 319 | buildSettings = { 320 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 321 | GCC_PREFIX_HEADER = "ContactPicker/ContactPicker-Prefix.pch"; 322 | INFOPLIST_FILE = "ContactPicker/ContactPicker-Info.plist"; 323 | IPHONEOS_DEPLOYMENT_TARGET = 6.0; 324 | PRODUCT_NAME = "$(TARGET_NAME)"; 325 | TARGETED_DEVICE_FAMILY = 1; 326 | WRAPPER_EXTENSION = app; 327 | }; 328 | name = Debug; 329 | }; 330 | 29A71C0316448250005286F1 /* Release */ = { 331 | isa = XCBuildConfiguration; 332 | buildSettings = { 333 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 334 | GCC_PREFIX_HEADER = "ContactPicker/ContactPicker-Prefix.pch"; 335 | INFOPLIST_FILE = "ContactPicker/ContactPicker-Info.plist"; 336 | IPHONEOS_DEPLOYMENT_TARGET = 6.0; 337 | PRODUCT_NAME = "$(TARGET_NAME)"; 338 | TARGETED_DEVICE_FAMILY = 1; 339 | WRAPPER_EXTENSION = app; 340 | }; 341 | name = Release; 342 | }; 343 | /* End XCBuildConfiguration section */ 344 | 345 | /* Begin XCConfigurationList section */ 346 | 29A71BDD1644824F005286F1 /* Build configuration list for PBXProject "ContactPicker" */ = { 347 | isa = XCConfigurationList; 348 | buildConfigurations = ( 349 | 29A71BFF16448250005286F1 /* Debug */, 350 | 29A71C0016448250005286F1 /* Release */, 351 | ); 352 | defaultConfigurationIsVisible = 0; 353 | defaultConfigurationName = Release; 354 | }; 355 | 29A71C0116448250005286F1 /* Build configuration list for PBXNativeTarget "ContactPicker" */ = { 356 | isa = XCConfigurationList; 357 | buildConfigurations = ( 358 | 29A71C0216448250005286F1 /* Debug */, 359 | 29A71C0316448250005286F1 /* Release */, 360 | ); 361 | defaultConfigurationIsVisible = 0; 362 | defaultConfigurationName = Release; 363 | }; 364 | /* End XCConfigurationList section */ 365 | }; 366 | rootObject = 29A71BDA1644824F005286F1 /* Project object */; 367 | } 368 | --------------------------------------------------------------------------------