diff --git a/AddressBook/AddressBook.xcodeproj/project.pbxproj b/AddressBook/AddressBook.xcodeproj/project.pbxproj index 12627df..adb46db 100644 --- a/AddressBook/AddressBook.xcodeproj/project.pbxproj +++ b/AddressBook/AddressBook.xcodeproj/project.pbxproj @@ -17,11 +17,11 @@ FA66282E187FE9EF00667C81 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FA66282D187FE9EF00667C81 /* Images.xcassets */; }; FA66284E187FEC1400667C81 /* Storyboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FA66284D187FEC1400667C81 /* Storyboard.storyboard */; }; FA662854187FEC7F00667C81 /* ListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FA662853187FEC7F00667C81 /* ListViewController.m */; }; - FA662858187FEC9600667C81 /* ContactViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FA662857187FEC9600667C81 /* ContactViewController.m */; }; FA66285C187FFACD00667C81 /* APAddressBook.m in Sources */ = {isa = PBXBuildFile; fileRef = FA66285B187FFACD00667C81 /* APAddressBook.m */; }; FA662860187FFB2100667C81 /* APContact.m in Sources */ = {isa = PBXBuildFile; fileRef = FA66285F187FFB2100667C81 /* APContact.m */; }; FA662862187FFCA700667C81 /* AddressBook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA662861187FFCA700667C81 /* AddressBook.framework */; }; FA66286618800DC500667C81 /* ContactTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = FA66286518800DC500667C81 /* ContactTableViewCell.m */; }; + FAD1CCE91881A18900D03475 /* ContactTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = FAD1CCE81881A18900D03475 /* ContactTableViewCell.xib */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -41,8 +41,6 @@ FA66284D187FEC1400667C81 /* Storyboard.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Storyboard.storyboard; sourceTree = ""; }; FA662852187FEC7F00667C81 /* ListViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ListViewController.h; sourceTree = ""; }; FA662853187FEC7F00667C81 /* ListViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ListViewController.m; sourceTree = ""; }; - FA662856187FEC9600667C81 /* ContactViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContactViewController.h; sourceTree = ""; }; - FA662857187FEC9600667C81 /* ContactViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ContactViewController.m; sourceTree = ""; }; FA66285A187FFACD00667C81 /* APAddressBook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = APAddressBook.h; path = ../Classes/APAddressBook.h; sourceTree = ""; }; FA66285B187FFACD00667C81 /* APAddressBook.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = APAddressBook.m; path = ../Classes/APAddressBook.m; sourceTree = ""; }; FA66285E187FFB2100667C81 /* APContact.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = APContact.h; path = ../Classes/APContact.h; sourceTree = ""; }; @@ -50,6 +48,8 @@ FA662861187FFCA700667C81 /* AddressBook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBook.framework; path = System/Library/Frameworks/AddressBook.framework; sourceTree = SDKROOT; }; FA66286418800DC500667C81 /* ContactTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContactTableViewCell.h; sourceTree = ""; }; FA66286518800DC500667C81 /* ContactTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ContactTableViewCell.m; sourceTree = ""; }; + FAD1CCE718819F7000D03475 /* APTypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = APTypes.h; path = ../Classes/APTypes.h; sourceTree = ""; }; + FAD1CCE81881A18900D03475 /* ContactTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ContactTableViewCell.xib; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -135,7 +135,6 @@ isa = PBXGroup; children = ( FA662855187FEC8500667C81 /* List */, - FA662859187FECBE00667C81 /* Contact */, ); name = Controllers; sourceTree = ""; @@ -158,15 +157,6 @@ name = List; sourceTree = ""; }; - FA662859187FECBE00667C81 /* Contact */ = { - isa = PBXGroup; - children = ( - FA662856187FEC9600667C81 /* ContactViewController.h */, - FA662857187FEC9600667C81 /* ContactViewController.m */, - ); - name = Contact; - sourceTree = ""; - }; FA66285D187FFAD300667C81 /* Classes */ = { isa = PBXGroup; children = ( @@ -174,6 +164,7 @@ FA66285B187FFACD00667C81 /* APAddressBook.m */, FA66285E187FFB2100667C81 /* APContact.h */, FA66285F187FFB2100667C81 /* APContact.m */, + FAD1CCE718819F7000D03475 /* APTypes.h */, ); name = Classes; sourceTree = ""; @@ -191,6 +182,7 @@ children = ( FA66286418800DC500667C81 /* ContactTableViewCell.h */, FA66286518800DC500667C81 /* ContactTableViewCell.m */, + FAD1CCE81881A18900D03475 /* ContactTableViewCell.xib */, ); name = Cells; sourceTree = ""; @@ -248,6 +240,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + FAD1CCE91881A18900D03475 /* ContactTableViewCell.xib in Resources */, FA662826187FE9EF00667C81 /* InfoPlist.strings in Resources */, FA66282E187FE9EF00667C81 /* Images.xcassets in Resources */, FA66284E187FEC1400667C81 /* Storyboard.storyboard in Resources */, @@ -297,7 +290,6 @@ FA66282C187FE9EF00667C81 /* AppDelegate.m in Sources */, FA66285C187FFACD00667C81 /* APAddressBook.m in Sources */, FA66286618800DC500667C81 /* ContactTableViewCell.m in Sources */, - FA662858187FEC9600667C81 /* ContactViewController.m in Sources */, FA662860187FFB2100667C81 /* APContact.m in Sources */, FA662828187FE9EF00667C81 /* main.m in Sources */, FA662854187FEC7F00667C81 /* ListViewController.m in Sources */, diff --git a/AddressBook/AddressBook/ContactTableViewCell.m b/AddressBook/AddressBook/ContactTableViewCell.m index be287fc..9158574 100644 --- a/AddressBook/AddressBook/ContactTableViewCell.m +++ b/AddressBook/AddressBook/ContactTableViewCell.m @@ -9,6 +9,14 @@ #import "ContactTableViewCell.h" #import "APContact.h" +@interface ContactTableViewCell () +@property (weak, nonatomic) IBOutlet UIImageView *photoView; +@property (weak, nonatomic) IBOutlet UILabel *nameLabel; +@property (weak, nonatomic) IBOutlet UILabel *companyLabel; +@property (weak, nonatomic) IBOutlet UILabel *phonesLabel; +@property (weak, nonatomic) IBOutlet UILabel *emailsLabel; +@end + @implementation ContactTableViewCell #pragma mark - life cycle @@ -28,8 +36,11 @@ - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reus - (void)updateWithModel:(id)model { APContact *contact = model; - self.textLabel.text = [self contactName:contact]; - self.detailTextLabel.text = [self contactPhones:contact]; + self.nameLabel.text = [self contactName:contact]; + self.companyLabel.text = contact.company ?: @"(No company)"; + self.phonesLabel.text = [self contactPhones:contact]; + self.emailsLabel.text = [self contactEmails:contact]; + self.photoView.image = contact.photo ?: [UIImage imageNamed:@"no_photo"]; } #pragma mark - private @@ -58,7 +69,19 @@ - (NSString *)contactPhones:(APContact *)contact } else { - return contact.phones.firstObject ?: @"No phones"; + return contact.phones.firstObject ?: @"(No phones)"; + } +} + +- (NSString *)contactEmails:(APContact *)contact +{ + if (contact.emails.count > 1) + { + return [contact.emails componentsJoinedByString:@", "]; + } + else + { + return contact.emails.firstObject ?: @"(No emails)"; } } diff --git a/AddressBook/AddressBook/ContactTableViewCell.xib b/AddressBook/AddressBook/ContactTableViewCell.xib new file mode 100644 index 0000000..37d08d3 --- /dev/null +++ b/AddressBook/AddressBook/ContactTableViewCell.xib @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AddressBook/AddressBook/ContactViewController.h b/AddressBook/AddressBook/ContactViewController.h deleted file mode 100644 index 413912f..0000000 --- a/AddressBook/AddressBook/ContactViewController.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// ContactViewController.h -// AddressBook -// -// Created by Alexey Belkevich on 1/10/14. -// Copyright (c) 2014 alterplay. All rights reserved. -// - -#import - -@interface ContactViewController : UIViewController - -@end diff --git a/AddressBook/AddressBook/ContactViewController.m b/AddressBook/AddressBook/ContactViewController.m deleted file mode 100644 index 6466f53..0000000 --- a/AddressBook/AddressBook/ContactViewController.m +++ /dev/null @@ -1,38 +0,0 @@ -// -// ContactViewController.m -// AddressBook -// -// Created by Alexey Belkevich on 1/10/14. -// Copyright (c) 2014 alterplay. All rights reserved. -// - -#import "ContactViewController.h" - -@interface ContactViewController () - -@end - -@implementation ContactViewController - -- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil -{ - self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; - if (self) { - // Custom initialization - } - return self; -} - -- (void)viewDidLoad -{ - [super viewDidLoad]; - // Do any additional setup after loading the view. -} - -- (void)didReceiveMemoryWarning -{ - [super didReceiveMemoryWarning]; - // Dispose of any resources that can be recreated. -} - -@end diff --git a/AddressBook/AddressBook/Images.xcassets/no_photo.imageset/Contents.json b/AddressBook/AddressBook/Images.xcassets/no_photo.imageset/Contents.json new file mode 100644 index 0000000..dbc32db --- /dev/null +++ b/AddressBook/AddressBook/Images.xcassets/no_photo.imageset/Contents.json @@ -0,0 +1,18 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "no_photo.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "no_photo@2x.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/AddressBook/AddressBook/Images.xcassets/no_photo.imageset/no_photo.png b/AddressBook/AddressBook/Images.xcassets/no_photo.imageset/no_photo.png new file mode 100644 index 0000000..1b2a3c2 Binary files /dev/null and b/AddressBook/AddressBook/Images.xcassets/no_photo.imageset/no_photo.png differ diff --git a/AddressBook/AddressBook/Images.xcassets/no_photo.imageset/no_photo@2x.png b/AddressBook/AddressBook/Images.xcassets/no_photo.imageset/no_photo@2x.png new file mode 100644 index 0000000..d4cebb5 Binary files /dev/null and b/AddressBook/AddressBook/Images.xcassets/no_photo.imageset/no_photo@2x.png differ diff --git a/AddressBook/AddressBook/ListViewController.m b/AddressBook/AddressBook/ListViewController.m index ff804a8..cca9f9c 100644 --- a/AddressBook/AddressBook/ListViewController.m +++ b/AddressBook/AddressBook/ListViewController.m @@ -10,7 +10,6 @@ #import "ContactTableViewCell.h" #import "APContact.h" #import "APAddressBook.h" -#import "ContactViewController.h" @interface ListViewController () @property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activity; @@ -43,12 +42,18 @@ - (void)viewDidLoad [self loadContacts]; } +#pragma mark - table view data source implementation + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath +{ + return 85.f; +} + #pragma mark - table view delegate implementation - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [self.tableView deselectRowAtIndexPath:indexPath animated:YES]; - [self performSegueWithIdentifier:NSStringFromClass(ContactViewController.class) sender:self]; } #pragma mark - private @@ -57,6 +62,14 @@ - (void)loadContacts { [self.activity startAnimating]; __weak __typeof(self) weakSelf = self; + addressBook.fieldsMask = APContactFieldAll; + addressBook.sortDescriptors = @[ + [NSSortDescriptor sortDescriptorWithKey:@"firstName" ascending:YES], + [NSSortDescriptor sortDescriptorWithKey:@"lastName" ascending:YES]]; + addressBook.filterBlock = ^BOOL(APContact *contact) + { + return contact.phones.count > 0; + }; [addressBook loadContacts:^(NSArray *contacts, NSError *error) { [weakSelf.activity stopAnimating]; diff --git a/AddressBook/AddressBook/Storyboard.storyboard b/AddressBook/AddressBook/Storyboard.storyboard index 8e0320b..0350ac2 100644 --- a/AddressBook/AddressBook/Storyboard.storyboard +++ b/AddressBook/AddressBook/Storyboard.storyboard @@ -64,32 +64,12 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/Classes/APAddressBook.h b/Classes/APAddressBook.h index e0ee3b3..b00429f 100644 --- a/Classes/APAddressBook.h +++ b/Classes/APAddressBook.h @@ -7,16 +7,16 @@ // #import +#import "APTypes.h" -typedef enum -{ - APAddressBookAccessUnknown = 0, - APAddressBookAccessGranted = 1, - APAddressBookAccessDenied = 2 -} APAddressBookAccess; +@class APContact; @interface APAddressBook : NSObject +@property (nonatomic, assign) APContactField fieldsMask; +@property (nonatomic, copy) APContactFilterBlock filterBlock; +@property (nonatomic, strong) NSArray *sortDescriptors; + + (APAddressBookAccess)access; - (void)loadContacts:(void (^)(NSArray *contacts, NSError *error))callbackBlock; diff --git a/Classes/APAddressBook.m b/Classes/APAddressBook.m index 4a5dc1c..eb8ce94 100644 --- a/Classes/APAddressBook.m +++ b/Classes/APAddressBook.m @@ -27,8 +27,10 @@ - (id)init _addressBook = ABAddressBookCreateWithOptions(NULL, error); if (error) { - NSLog(@"%@", CFErrorCopyFailureReason(*error)); + NSLog(@"%@", (__bridge_transfer NSString *)CFErrorCopyFailureReason(*error)); + return nil; } + self.fieldsMask = APContactFieldDefault; } return self; } @@ -63,6 +65,9 @@ + (APAddressBookAccess)access - (void)loadContacts:(void (^)(NSArray *contacts, NSError *error))callbackBlock { __weak __typeof (self) weakSelf = self; + APContactField fieldMask = self.fieldsMask; + NSArray *descriptors = self.sortDescriptors; + APContactFilterBlock filterBlock = self.filterBlock; ABAddressBookRequestAccessWithCompletion(self.addressBook, ^(bool granted, CFErrorRef errorRef) { NSArray *array = nil; @@ -75,12 +80,14 @@ - (void)loadContacts:(void (^)(NSArray *contacts, NSError *error))callbackBlock for (NSUInteger i = 0; i < contactCount; i++) { ABRecordRef recordRef = CFArrayGetValueAtIndex(peopleArrayRef, i); - APContact *contact = [[APContact alloc] init]; - contact.firstName = [APAddressBook firstNameFromRecord:recordRef]; - contact.lastName = [APAddressBook lastNameFromRecord:recordRef]; - contact.phones = [APAddressBook phonesFromRecord:recordRef]; - [contacts addObject:contact]; + APContact *contact = [[APContact alloc] initWithRecordRef:recordRef + fieldMask:fieldMask]; + if (!filterBlock || filterBlock(contact)) + { + [contacts addObject:contact]; + } } + [contacts sortUsingDescriptors:descriptors]; array = contacts.copy; } else if (errorRef) @@ -98,36 +105,4 @@ - (void)loadContacts:(void (^)(NSArray *contacts, NSError *error))callbackBlock }); } -#pragma mark - private - -+ (NSString *)firstNameFromRecord:(ABRecordRef)recordRef -{ - CFTypeRef valueRef = (ABRecordCopyValue(recordRef, kABPersonFirstNameProperty)); - return (__bridge_transfer NSString *)valueRef; -} - -+ (NSString *)lastNameFromRecord:(ABRecordRef)recordRef -{ - CFTypeRef valueRef = (ABRecordCopyValue(recordRef, kABPersonLastNameProperty)); - return (__bridge_transfer NSString *)valueRef; -} - -+ (NSArray *)phonesFromRecord:(ABRecordRef)recordRef -{ - ABMultiValueRef phonesValueRef = ABRecordCopyValue(recordRef, kABPersonPhoneProperty); - NSUInteger phonesCount = (NSUInteger)ABMultiValueGetCount(phonesValueRef); - NSMutableArray *phones = [[NSMutableArray alloc] init]; - for (NSUInteger i = 0; i < phonesCount; i++) - { - CFTypeRef value = ABMultiValueCopyValueAtIndex(phonesValueRef, i); - NSString *number = (__bridge_transfer NSString *)value; - if (number) - { - [phones addObject:number]; - } - } - CFRelease(phonesValueRef); - return phones.copy; -} - @end diff --git a/Classes/APContact.h b/Classes/APContact.h index 9f5949c..92ce8f7 100644 --- a/Classes/APContact.h +++ b/Classes/APContact.h @@ -7,11 +7,19 @@ // #import +#import +#import "APTypes.h" @interface APContact : NSObject -@property (nonatomic, strong) NSString *firstName; -@property (nonatomic, strong) NSString *lastName; -@property (nonatomic, strong) NSArray *phones; +@property (nonatomic, readonly) APContactField fieldMask; +@property (nonatomic, readonly) NSString *firstName; +@property (nonatomic, readonly) NSString *lastName; +@property (nonatomic, readonly) NSString *company; +@property (nonatomic, readonly) NSArray *phones; +@property (nonatomic, readonly) NSArray *emails; +@property (nonatomic, readonly) UIImage *photo; + +- (id)initWithRecordRef:(ABRecordRef)recordRef fieldMask:(APContactField)fieldMask; @end diff --git a/Classes/APContact.m b/Classes/APContact.m index e132aeb..e2c8b05 100644 --- a/Classes/APContact.m +++ b/Classes/APContact.m @@ -10,4 +10,66 @@ @implementation APContact +#pragma mark - life cycle + +- (id)initWithRecordRef:(ABRecordRef)recordRef fieldMask:(APContactField)fieldMask +{ + self = [super init]; + if (self) + { + _fieldMask = fieldMask; + if (fieldMask & APContactFieldFirstName) + { + _firstName = [self stringProperty:kABPersonFirstNameProperty fromRecord:recordRef]; + } + if (fieldMask & APContactFieldLastName) + { + _lastName = [self stringProperty:kABPersonLastNameProperty fromRecord:recordRef]; + } + if (fieldMask & APContactFieldCompany) + { + _company = [self stringProperty:kABPersonOrganizationProperty fromRecord:recordRef]; + } + if (fieldMask & APContactFieldPhones) + { + _phones = [self arrayProperty:kABPersonPhoneProperty fromRecord:recordRef]; + } + if (fieldMask & APContactFieldEmails) + { + _emails = [self arrayProperty:kABPersonEmailProperty fromRecord:recordRef]; + } + if (fieldMask & APContactFieldPhoto) + { + NSData *imageData = (__bridge_transfer NSData *)ABPersonCopyImageData(recordRef); + _photo = [UIImage imageWithData:imageData scale:UIScreen.mainScreen.scale]; + } + } + return self; +} + +#pragma mark - private + +- (NSString *)stringProperty:(ABPropertyID)property fromRecord:(ABRecordRef)recordRef +{ + CFTypeRef valueRef = (ABRecordCopyValue(recordRef, property)); + return (__bridge_transfer NSString *)valueRef; +} + +- (NSArray *)arrayProperty:(ABPropertyID)property fromRecord:(ABRecordRef)recordRef +{ + ABMultiValueRef multiValue = ABRecordCopyValue(recordRef, property); + NSUInteger count = (NSUInteger)ABMultiValueGetCount(multiValue); + NSMutableArray *array = [[NSMutableArray alloc] init]; + for (NSUInteger i = 0; i < count; i++) + { + CFTypeRef value = ABMultiValueCopyValueAtIndex(multiValue, i); + NSString *string = (__bridge_transfer NSString *)value; + if (string) + { + [array addObject:string]; + } + } + CFRelease(multiValue); + return array.copy; +} @end diff --git a/Classes/APTypes.h b/Classes/APTypes.h new file mode 100644 index 0000000..4cef5be --- /dev/null +++ b/Classes/APTypes.h @@ -0,0 +1,37 @@ +// +// APTypes.h +// AddressBook +// +// Created by Alexey Belkevich on 1/11/14. +// Copyright (c) 2014 alterplay. All rights reserved. +// + +#ifndef AddressBook_APTypes_h +#define AddressBook_APTypes_h + +@class APContact; + +typedef enum +{ + APAddressBookAccessUnknown = 0, + APAddressBookAccessGranted = 1, + APAddressBookAccessDenied = 2 +} APAddressBookAccess; + +typedef BOOL(^APContactFilterBlock)(APContact *contact); + +typedef enum +{ + APContactFieldFirstName = 1 << 0, + APContactFieldLastName = 1 << 1, + APContactFieldCompany = 1 << 2, + APContactFieldPhones = 1 << 3, + APContactFieldEmails = 1 << 4, + APContactFieldPhoto = 1 << 5, + APContactFieldDefault = APContactFieldFirstName | APContactFieldLastName | + APContactFieldPhones, + APContactFieldAll = APContactFieldDefault | APContactFieldCompany | + APContactFieldEmails | APContactFieldPhoto +} APContactField; + +#endif