├── Default-568h@2x.png ├── RHAddressBookLogicTests ├── en.lproj │ └── InfoPlist.strings ├── unit_test_person_image_1.jpg ├── unit_test_person_image_2.jpg ├── unit_test_person_image_1_thumb.jpg ├── unit_test_person_image_2_thumb.jpg ├── RHAddressBookLogicTests-Prefix.pch ├── RHAddressBookLogicTests-Info.plist ├── UIImage+RHResizingAdditions.h ├── UIImage+RHPixelAdditions.h ├── UIImage+RHComparingAdditions.h ├── UIImage+RHResizingAdditions.m ├── UIImage+RHPixelAdditions.m ├── UIImage+RHComparingAdditions.m └── RHAddressBookLogicTests.h ├── RHAddressBookTester ├── en.lproj │ └── InfoPlist.strings ├── main.m ├── RHAppDelegate.h ├── RHGroupViewController.h ├── RHAddressBookTester-Prefix.pch ├── RHAddressBookViewController.h ├── RHAddressBookTester-Info.plist ├── RHAppDelegate.m ├── RHGroupViewController.m └── RHAddressBookViewController.m ├── .gitignore ├── RHAddressBook.xcodeproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── RHAddressBook ├── RHAddressBook-Prefix.pch ├── RHAddressBook-Info.plist ├── AddressBook.h ├── RHAddressBookThreadMain.h ├── RHRecord_Private.h ├── NSThread+RHBlockAdditions.h ├── RHAddressBook_Private.h ├── RHAddressBookThreadMain.m ├── RHAddressBookSharedServices.h ├── RHAddressBookGeoResult.h ├── RHSource.h ├── RHGroup.h ├── NSThread+RHBlockAdditions.m ├── RHRecord.h ├── RHARCSupport.h ├── RHMultiValue.h ├── RHSource.m ├── RHMultiValue.m ├── RHPersonLabels.h ├── RHGroup.m ├── RHAddressBookGeoResult.m ├── RHAddressBook.h ├── RHPerson.h ├── RHRecord.m └── RHAddressBookSharedServices.m ├── LICENSE └── README.md /Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heardrwt/RHAddressBook/HEAD/Default-568h@2x.png -------------------------------------------------------------------------------- /RHAddressBookLogicTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /RHAddressBookTester/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | RHAddressBook.xcodeproj/xcuserdata 2 | RHAddressBook.xcodeproj/project.xcworkspace/xcuserdata 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /RHAddressBookLogicTests/unit_test_person_image_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heardrwt/RHAddressBook/HEAD/RHAddressBookLogicTests/unit_test_person_image_1.jpg -------------------------------------------------------------------------------- /RHAddressBookLogicTests/unit_test_person_image_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heardrwt/RHAddressBook/HEAD/RHAddressBookLogicTests/unit_test_person_image_2.jpg -------------------------------------------------------------------------------- /RHAddressBookLogicTests/unit_test_person_image_1_thumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heardrwt/RHAddressBook/HEAD/RHAddressBookLogicTests/unit_test_person_image_1_thumb.jpg -------------------------------------------------------------------------------- /RHAddressBookLogicTests/unit_test_person_image_2_thumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heardrwt/RHAddressBook/HEAD/RHAddressBookLogicTests/unit_test_person_image_2_thumb.jpg -------------------------------------------------------------------------------- /RHAddressBook.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /RHAddressBookLogicTests/RHAddressBookLogicTests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'RHAddressBookLogicTests' target in the 'RHAddressBookLogicTests' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #import 8 | #endif 9 | -------------------------------------------------------------------------------- /RHAddressBook/RHAddressBook-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'RHAddressBook' target in the 'RHAddressBook' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | 8 | //ARC Macro Support 9 | #import "RHARCSupport.h" 10 | #endif 11 | 12 | -------------------------------------------------------------------------------- /RHAddressBookTester/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // RHAddressBookTester 4 | // 5 | // Created by Richard Heard on 13/11/11. 6 | // Copyright (c) 2011 Richard Heard. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "RHAppDelegate.h" 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([RHAppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /RHAddressBookTester/RHAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // RHAppDelegate.h 3 | // RHAddressBookTester 4 | // 5 | // Created by Richard Heard on 13/11/11. 6 | // Copyright (c) 2011 Richard Heard. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RHAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @property (strong, nonatomic) UINavigationController *navigationController; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /RHAddressBookTester/RHGroupViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // RHGroupViewController.h 3 | // RHAddressBook 4 | // 5 | // Created by Richard Heard on 21/02/12. 6 | // Copyright (c) 2012 Richard Heard. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RHGroupViewController : UITableViewController { 12 | RHGroup *_group; 13 | 14 | NSMutableArray *_members; //cache 15 | } 16 | 17 | @property (retain) RHGroup *group; 18 | 19 | - (instancetype)initWithGroup:(RHGroup*)group NS_DESIGNATED_INITIALIZER; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /RHAddressBookTester/RHAddressBookTester-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'RHAddressBookTester' target in the 'RHAddressBookTester' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_4_0 8 | #warning "This project uses features only available in iOS SDK 4.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #import 15 | #import 16 | #import 17 | #endif 18 | -------------------------------------------------------------------------------- /RHAddressBookLogicTests/RHAddressBookLogicTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.rheard.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /RHAddressBook/RHAddressBook-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.rheard.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /RHAddressBookTester/RHAddressBookViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // RHAddressBookViewController.h 3 | // RHAddressBook 4 | // 5 | // Created by Richard Heard on 20/02/12. 6 | // Copyright (c) 2012 Richard Heard. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef NS_ENUM(NSInteger, RHAddressBookViewControllerSections) { 12 | kRHAddressBookViewControllerSourcesSection, 13 | kRHAddressBookViewControllerGroupsSection, 14 | kRHAddressBookViewControllerPeopleSection, 15 | kRHAddressBookViewControllerLocationSection, 16 | kRHAddressBookViewControllerInfoSection, 17 | kRHAddressBookViewControllerNumberOfSections 18 | } ; 19 | 20 | 21 | #define kRHAddressBookViewControllerInfoCellsCount 2 22 | 23 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 24 | #define kRHAddressBookViewControllerLocationCellsCount 4 25 | #else 26 | #define kRHAddressBookViewControllerLocationCellsCount 1 27 | #endif 28 | 29 | 30 | @interface RHAddressBookViewController : UITableViewController { 31 | 32 | RHAddressBook *_addressBook; 33 | 34 | //cache 35 | NSMutableArray *_sources; 36 | NSMutableArray *_groups; 37 | NSMutableArray *_people; 38 | 39 | } 40 | 41 | -(instancetype)initWithAddressBook:(RHAddressBook*)addressBook NS_DESIGNATED_INITIALIZER; 42 | 43 | @property (retain, nonatomic) RHAddressBook *addressBook; 44 | 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | RHAddressBook 2 | 3 | Copyright (c) 2011-2012 Richard Heard. All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions 7 | are met: 8 | 1. Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 3. The name of the author may not be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 21 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /RHAddressBookTester/RHAddressBookTester-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIconFiles 12 | 13 | CFBundleIdentifier 14 | com.rheard.${PRODUCT_NAME:rfc1034identifier} 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | ${PRODUCT_NAME} 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | 1.0 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | 1.0 27 | LSRequiresIPhoneOS 28 | 29 | NSHumanReadableCopyright 30 | Copyright (c) 2012 Richard Heard. All rights reserved. 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /RHAddressBook/AddressBook.h: -------------------------------------------------------------------------------- 1 | // 2 | // AddressBook.h 3 | // RHAddressBook 4 | // 5 | // Created by Richard Heard on 13/11/11. 6 | // Copyright (c) 2011 Richard Heard. All rights reserved. 7 | // 8 | // Redistribution and use in source and binary forms, with or without 9 | // modification, are permitted provided that the following conditions 10 | // are met: 11 | // 1. Redistributions of source code must retain the above copyright 12 | // notice, this list of conditions and the following disclaimer. 13 | // 2. Redistributions in binary form must reproduce the above copyright 14 | // notice, this list of conditions and the following disclaimer in the 15 | // documentation and/or other materials provided with the distribution. 16 | // 3. The name of the author may not be used to endorse or promote products 17 | // derived from this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | // 30 | 31 | #import "RHAddressBook.h" 32 | #import "RHRecord.h" 33 | #import "RHSource.h" 34 | #import "RHGroup.h" 35 | #import "RHPerson.h" 36 | #import "RHMultiValue.h" 37 | -------------------------------------------------------------------------------- /RHAddressBook/RHAddressBookThreadMain.h: -------------------------------------------------------------------------------- 1 | // 2 | // RHAddressBookThreadMain.h 3 | // RHAddressBook 4 | // 5 | // Created by Richard Heard on 11/02/12. 6 | // Copyright (c) 2012 Richard Heard. All rights reserved. 7 | // 8 | // Redistribution and use in source and binary forms, with or without 9 | // modification, are permitted provided that the following conditions 10 | // are met: 11 | // 1. Redistributions of source code must retain the above copyright 12 | // notice, this list of conditions and the following disclaimer. 13 | // 2. Redistributions in binary form must reproduce the above copyright 14 | // notice, this list of conditions and the following disclaimer in the 15 | // documentation and/or other materials provided with the distribution. 16 | // 3. The name of the author may not be used to endorse or promote products 17 | // derived from this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | // 30 | 31 | #import 32 | 33 | @interface RHAddressBookThreadMain : NSObject 34 | 35 | -(void)threadMain:(NSString*)logMessage; //runloop for the ab thread 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /RHAddressBook/RHRecord_Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // RHRecord_Private.h 3 | // RHAddressBook 4 | // 5 | // Created by Richard Heard on 20/02/12. 6 | // Copyright (c) 2011 Richard Heard. All rights reserved. 7 | // 8 | // Redistribution and use in source and binary forms, with or without 9 | // modification, are permitted provided that the following conditions 10 | // are met: 11 | // 1. Redistributions of source code must retain the above copyright 12 | // notice, this list of conditions and the following disclaimer. 13 | // 2. Redistributions in binary form must reproduce the above copyright 14 | // notice, this list of conditions and the following disclaimer in the 15 | // documentation and/or other materials provided with the distribution. 16 | // 3. The name of the author may not be used to endorse or promote products 17 | // derived from this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | // 30 | 31 | #import "RHRecord.h" 32 | #import 33 | 34 | @class RHAddressBook; 35 | 36 | @interface RHRecord () 37 | 38 | //init (you should not call init on these objects yourself. instead use the [RHAddressBook newPerson] method) 39 | -(instancetype)initWithAddressBook:(RHAddressBook*)addressBook recordRef:(ABRecordRef)recordRef /* NS_DESIGNATED_INITIALIZER (Xcode5: see http://permalink.gmane.org/gmane.comp.compilers.clang.scm/94822) */; 40 | 41 | @end 42 | 43 | -------------------------------------------------------------------------------- /RHAddressBookLogicTests/UIImage+RHResizingAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+RHResizingAdditions.h 3 | // RHKit 4 | // 5 | // Created by Richard Heard on 18/03/12. 6 | // Copyright (c) 2012 Richard Heard. All rights reserved. 7 | // 8 | // Redistribution and use in source and binary forms, with or without 9 | // modification, are permitted provided that the following conditions 10 | // are met: 11 | // 1. Redistributions of source code must retain the above copyright 12 | // notice, this list of conditions and the following disclaimer. 13 | // 2. Redistributions in binary form must reproduce the above copyright 14 | // notice, this list of conditions and the following disclaimer in the 15 | // documentation and/or other materials provided with the distribution. 16 | // 3. The name of the author may not be used to endorse or promote products 17 | // derived from this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | // 30 | 31 | // Supports resizing of UIImages that are backed by a CGImage, preserves rotation metadata and scale. 32 | 33 | #import 34 | #import 35 | 36 | @interface UIImage (RHResizingAdditions) 37 | 38 | 39 | -(UIImage *)imageResizedToSize:(CGSize)size; //resize the image, preserving image rotation and scale metadata 40 | 41 | @end 42 | 43 | 44 | // underlying implementation 45 | 46 | UIImage * UIImageResizeImageToSize(UIImage *image, CGSize size); 47 | -------------------------------------------------------------------------------- /RHAddressBook/NSThread+RHBlockAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSThread+RHBlockAdditions.h 3 | // RHAddressBook 4 | // 5 | // Created by Richard Heard on 22/8/11. 6 | // Copyright (c) 2011 Richard Heard. All rights reserved. 7 | // 8 | // Redistribution and use in source and binary forms, with or without 9 | // modification, are permitted provided that the following conditions 10 | // are met: 11 | // 1. Redistributions of source code must retain the above copyright 12 | // notice, this list of conditions and the following disclaimer. 13 | // 2. Redistributions in binary form must reproduce the above copyright 14 | // notice, this list of conditions and the following disclaimer in the 15 | // documentation and/or other materials provided with the distribution. 16 | // 3. The name of the author may not be used to endorse or promote products 17 | // derived from this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | // 30 | 31 | // Provides methods for running blocks on specific threads, useful when specific 32 | // tasks need to be performed on a single thread for thread safety etc. 33 | 34 | #import 35 | 36 | typedef void (^VoidBlock)(void); 37 | 38 | @interface NSThread (RHBlockAdditions) 39 | 40 | -(void)rh_performBlock:(VoidBlock)block; 41 | -(void)rh_performBlock:(VoidBlock)block waitUntilDone:(BOOL)wait; 42 | -(void)rh_performBlock:(VoidBlock)block afterDelay:(NSTimeInterval)delay; 43 | 44 | +(void)rh_performBlockOnMainThread:(VoidBlock)block; 45 | +(void)rh_performBlockOnMainThread:(VoidBlock)block waitUntilDone:(BOOL)wait; 46 | +(void)rh_performBlockInBackground:(VoidBlock)block; 47 | 48 | //private 49 | -(void)_rh_runBlock:(void (^)())block; 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /RHAddressBookLogicTests/UIImage+RHPixelAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+RHPixelAdditions.h 3 | // RHKit 4 | // 5 | // Created by Richard Heard on 18/03/12. 6 | // Copyright (c) 2012 Richard Heard. All rights reserved. 7 | // 8 | // Redistribution and use in source and binary forms, with or without 9 | // modification, are permitted provided that the following conditions 10 | // are met: 11 | // 1. Redistributions of source code must retain the above copyright 12 | // notice, this list of conditions and the following disclaimer. 13 | // 2. Redistributions in binary form must reproduce the above copyright 14 | // notice, this list of conditions and the following disclaimer in the 15 | // documentation and/or other materials provided with the distribution. 16 | // 3. The name of the author may not be used to endorse or promote products 17 | // derived from this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | // 30 | 31 | // Supports accessing the underlying raw pixel data of a UIImage, both in bulk and on a point by point basis. 32 | // Returned format is RGBA premultipled as is supported by CoreGraphics. 33 | 34 | #import 35 | #import 36 | 37 | 38 | @interface UIImage (RHPixelAdditions) 39 | 40 | 41 | -(NSData *)rgba; 42 | -(NSData *)rgbaForPoint:(CGPoint)point; 43 | 44 | @end 45 | 46 | 47 | //underlying implementation 48 | 49 | typedef struct pixel { 50 | uint8_t R; 51 | uint8_t G; 52 | uint8_t B; 53 | uint8_t A; 54 | } pixel; 55 | 56 | pixel * UIImageCopyRGBAForImage(UIImage *image); 57 | pixel * UIImageCopyRGBAForPointInImage(CGPoint point, UIImage*image); 58 | 59 | NSData * UIImageGetRGBAForImage(UIImage* image); 60 | NSData * UIImageGetRGBAForPointInImage(CGPoint point, UIImage* image); 61 | 62 | -------------------------------------------------------------------------------- /RHAddressBook/RHAddressBook_Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // RHAddressBook_Private.h 3 | // RHAddressBook 4 | // 5 | // Created by Richard Heard on 11/11/11. 6 | // Copyright (c) 2011 Richard Heard. All rights reserved. 7 | // 8 | // Redistribution and use in source and binary forms, with or without 9 | // modification, are permitted provided that the following conditions 10 | // are met: 11 | // 1. Redistributions of source code must retain the above copyright 12 | // notice, this list of conditions and the following disclaimer. 13 | // 2. Redistributions in binary form must reproduce the above copyright 14 | // notice, this list of conditions and the following disclaimer in the 15 | // documentation and/or other materials provided with the distribution. 16 | // 3. The name of the author may not be used to endorse or promote products 17 | // derived from this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | // 30 | 31 | #import "RHAddressBook.h" 32 | 33 | @class RHRecord; 34 | @interface RHAddressBook () 35 | 36 | //used by RHRecord objects upon init / dealloc, so that the addressbook class is made aware of an object being created or destroyed and can add/remove it from its weakly linked cache 37 | -(void)_recordCheckIn:(RHRecord*)record; 38 | -(void)_recordCheckOut:(RHRecord*)record; 39 | 40 | @property (nonatomic, readonly) dispatch_queue_t addressBookQueue; //serial queue for thread safety. 41 | 42 | @end 43 | 44 | //use this, in combination with addressBookQueue for thread safety when messing with the ab directly 45 | extern void rh_dispatch_sync_for_addressbook(RHAddressBook *addressbook, dispatch_block_t block); 46 | 47 | //returns YES if currently being executed on the addressbooks addressBookQueue, otherwise NO. 48 | extern BOOL rh_dispatch_is_current_queue_for_addressbook(RHAddressBook *addressBook); 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /RHAddressBook/RHAddressBookThreadMain.m: -------------------------------------------------------------------------------- 1 | // 2 | // RHAddressBookThreadMain.m 3 | // RHAddressBook 4 | // 5 | // Created by Richard Heard on 11/02/12. 6 | // Copyright (c) 2012 Richard Heard. All rights reserved. 7 | // 8 | // Redistribution and use in source and binary forms, with or without 9 | // modification, are permitted provided that the following conditions 10 | // are met: 11 | // 1. Redistributions of source code must retain the above copyright 12 | // notice, this list of conditions and the following disclaimer. 13 | // 2. Redistributions in binary form must reproduce the above copyright 14 | // notice, this list of conditions and the following disclaimer in the 15 | // documentation and/or other materials provided with the distribution. 16 | // 3. The name of the author may not be used to endorse or promote products 17 | // derived from this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | // 30 | 31 | #import "RHAddressBookThreadMain.h" 32 | 33 | #import "RHAddressBook.h" 34 | 35 | @implementation RHAddressBookThreadMain 36 | 37 | -(void)threadMain:(NSString*)logMessage{ 38 | @autoreleasepool { 39 | RHLog(@"spawned thread: %@", [NSThread currentThread]); 40 | //schedule a timer on the runloop so it wont return immediately 41 | NSTimer *timer = [NSTimer timerWithTimeInterval:10.0 invocation:nil repeats:YES]; 42 | [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; 43 | 44 | do { 45 | [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; 46 | if (logMessage) RHLog(@"%@", logMessage); 47 | } while (![[NSThread currentThread] isCancelled]); //until we are cancelled 48 | 49 | RHLog(@"terminated thread: %@", [NSThread currentThread]); 50 | 51 | [timer invalidate]; //invalidate the timer that was keeping our runloop alive 52 | } 53 | //done 54 | } 55 | 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /RHAddressBookLogicTests/UIImage+RHComparingAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+RHComparingAdditions.h 3 | // RHKit 4 | // 5 | // Created by Richard Heard on 18/03/12. 6 | // Copyright (c) 2012 Richard Heard. All rights reserved. 7 | // 8 | // Redistribution and use in source and binary forms, with or without 9 | // modification, are permitted provided that the following conditions 10 | // are met: 11 | // 1. Redistributions of source code must retain the above copyright 12 | // notice, this list of conditions and the following disclaimer. 13 | // 2. Redistributions in binary form must reproduce the above copyright 14 | // notice, this list of conditions and the following disclaimer in the 15 | // documentation and/or other materials provided with the distribution. 16 | // 3. The name of the author may not be used to endorse or promote products 17 | // derived from this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | // 30 | 31 | // Pixel level image comparison, supports various thresholds for slight variations in images (eg from colour correction etc.) 32 | // also supports resizing one of the 2 images being compared if they are not currently the same size, so that a px by px comparison is possible. 33 | 34 | #import 35 | 36 | #import "UIImage+RHPixelAdditions.h" 37 | #import "UIImage+RHResizingAdditions.h" 38 | 39 | @interface UIImage (RHComparingAdditions) 40 | 41 | 42 | -(CGFloat)percentageDifferenceBetweenImage:(UIImage *)image; //use a default tolerance of 0.1f; (10% of 255 so 25 steps) 43 | -(CGFloat)percentageDifferenceBetweenImage:(UIImage *)image withTolerance:(CGFloat)percentageTolerance; //no scale (ie mismatched images return 1.0f) 44 | -(CGFloat)percentageDifferenceBetweenImage:(UIImage *)image withTolerance:(CGFloat)percentageTolerance andScaleIfImageSizesMismatched:(BOOL)shouldScale; 45 | 46 | @end 47 | 48 | 49 | 50 | #pragma mark - underlying implementation 51 | 52 | CGFloat UIImagePercentageDifferenceBetweenImages(UIImage* image1, UIImage* image2); // default tolerance of 25 (10% of of 255) 53 | CGFloat UIImagePercentageDifferenceBetweenImagesWithTolerance(UIImage* image1, UIImage* image2, CGFloat percentageTolerance, BOOL shouldScaleIfImageSizesMismatched); -------------------------------------------------------------------------------- /RHAddressBook/RHAddressBookSharedServices.h: -------------------------------------------------------------------------------- 1 | // 2 | // RHAddressBookSharedServices.h 3 | // RHAddressBook 4 | // 5 | // Created by Richard Heard on 11/11/11. 6 | // Copyright (c) 2011 Richard Heard. All rights reserved. 7 | // 8 | // Redistribution and use in source and binary forms, with or without 9 | // modification, are permitted provided that the following conditions 10 | // are met: 11 | // 1. Redistributions of source code must retain the above copyright 12 | // notice, this list of conditions and the following disclaimer. 13 | // 2. Redistributions in binary form must reproduce the above copyright 14 | // notice, this list of conditions and the following disclaimer in the 15 | // documentation and/or other materials provided with the distribution. 16 | // 3. The name of the author may not be used to endorse or promote products 17 | // derived from this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | // 30 | 31 | #import 32 | #import 33 | #import 34 | 35 | #import "RHAddressBook.h" 36 | 37 | @class RHAddressBookGeoResult; 38 | 39 | @interface RHAddressBookSharedServices : NSObject 40 | +(id)sharedInstance; 41 | 42 | #if RH_AB_INCLUDE_GEOCODING 43 | 44 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 45 | 46 | //location lookup (iOS5+) 47 | //forward 48 | -(CLPlacemark*)placemarkForPersonID:(ABRecordID)personID addressID:(ABMultiValueIdentifier)addressID; 49 | -(CLLocation*)locationForPersonID:(ABRecordID)personID addressID:(ABMultiValueIdentifier)addressID; 50 | 51 | //reverse 52 | -(NSArray*)geoResultsWithinDistance:(CLLocationDistance)distance ofLocation:(CLLocation*)location; //returns RHAddressBookGeoResult objects 53 | -(RHAddressBookGeoResult*)geoResultClosestToLocation:(CLLocation*)location; 54 | -(RHAddressBookGeoResult*)geoResultClosestToLocation:(CLLocation*)location distanceOut:(CLLocationDistance*)distanceOut; 55 | 56 | #endif //end iOS5+ 57 | 58 | //geocode cache processing (addresses are geocoded for future queries and cached locally 59 | +(BOOL)isPreemptiveGeocodingEnabled; 60 | +(void)setPreemptiveGeocodingEnabled:(BOOL)enabled; 61 | @property (nonatomic, readonly) float preemptiveGeocodingProgress; 62 | +(BOOL)isGeocodingSupported; 63 | 64 | #endif //end Geocoding 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /RHAddressBook/RHAddressBookGeoResult.h: -------------------------------------------------------------------------------- 1 | // 2 | // RHAddressBookGeoResult.h 3 | // RHAddressBook 4 | // 5 | // Created by Richard Heard on 12/11/11. 6 | // Copyright (c) 2011 Richard Heard. All rights reserved. 7 | // 8 | // Redistribution and use in source and binary forms, with or without 9 | // modification, are permitted provided that the following conditions 10 | // are met: 11 | // 1. Redistributions of source code must retain the above copyright 12 | // notice, this list of conditions and the following disclaimer. 13 | // 2. Redistributions in binary form must reproduce the above copyright 14 | // notice, this list of conditions and the following disclaimer in the 15 | // documentation and/or other materials provided with the distribution. 16 | // 3. The name of the author may not be used to endorse or promote products 17 | // derived from this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | // 30 | 31 | #import "RHAddressBook.h" 32 | 33 | #if RH_AB_INCLUDE_GEOCODING 34 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 35 | 36 | #import 37 | #import 38 | #import 39 | 40 | @class CLPlacemark; 41 | @class CLGeocoder; 42 | 43 | @interface RHAddressBookGeoResult : NSObject 44 | 45 | @property (assign, readonly) ABRecordID personID; 46 | @property (assign, readonly) ABMultiValueIdentifier addressID; 47 | @property (retain, readonly) NSString *addressHash; 48 | 49 | @property (retain, readonly) CLLocation *location; 50 | @property (retain, readonly) CLPlacemark *placemark; //we only store the most accurate placemark 51 | @property (assign, readonly) BOOL resultNotFound; 52 | 53 | //initializer... only time the ids can be set, auto calculates its address hash 54 | -(instancetype)initWithPersonID:(ABRecordID)personID addressID:(ABMultiValueIdentifier)addressID NS_DESIGNATED_INITIALIZER; 55 | 56 | //used to determine if the stored address hash matches the corresponding address in the current AddressBook 57 | -(BOOL)isValid; 58 | 59 | -(NSDictionary*)associatedAddressDictionary; //performs a realtime lookup based on the stored ids 60 | 61 | //geocode 62 | -(void)geocodeAssociatedAddressDictionary; //performs a geocode on the object 63 | 64 | //hashing 65 | +(NSString*)hashForDictionary:(NSDictionary*)dict; 66 | +(NSString*)hashForString:(NSString*)string; 67 | 68 | @end 69 | 70 | #endif //end iOS5+ 71 | #endif //end Geocoding 72 | -------------------------------------------------------------------------------- /RHAddressBook/RHSource.h: -------------------------------------------------------------------------------- 1 | // 2 | // RHSource.h 3 | // RHAddressBook 4 | // 5 | // Created by Richard Heard on 11/11/11. 6 | // Copyright (c) 2011 Richard Heard. All rights reserved. 7 | // 8 | // Redistribution and use in source and binary forms, with or without 9 | // modification, are permitted provided that the following conditions 10 | // are met: 11 | // 1. Redistributions of source code must retain the above copyright 12 | // notice, this list of conditions and the following disclaimer. 13 | // 2. Redistributions in binary form must reproduce the above copyright 14 | // notice, this list of conditions and the following disclaimer in the 15 | // documentation and/or other materials provided with the distribution. 16 | // 3. The name of the author may not be used to endorse or promote products 17 | // derived from this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | // 30 | 31 | #import "RHRecord.h" 32 | #import 33 | #import 34 | @class RHAddressBook; 35 | @class RHPerson; 36 | @class RHGroup; 37 | 38 | @interface RHSource : RHRecord 39 | 40 | //properties 41 | @property (copy, readonly) NSString *name; 42 | @property (readonly) ABSourceType type; 43 | 44 | //access groups in the current source (this method just forwards to the equivalent method on RHAddressBook) 45 | @property (nonatomic, readonly, copy) NSArray *groups; 46 | 47 | //access people in the current source 48 | @property (nonatomic, readonly, copy) NSArray *people; 49 | -(NSArray*)peopleOrderedBySortOrdering:(ABPersonSortOrdering)ordering; 50 | @property (nonatomic, readonly, copy) NSArray *peopleOrderedByFirstName; 51 | @property (nonatomic, readonly, copy) NSArray *peopleOrderedByLastName; 52 | @property (nonatomic, readonly, copy) NSArray *peopleOrderedByUsersPreference; 53 | 54 | //add (these methods just fwd to the equivalent convenience methods on RHAddressBook, including adding to the addressbook) 55 | -(RHPerson*)newPerson; //returns nil on error (eg read only source) 56 | -(RHGroup*)newGroup; //returns nil on error (eg read only source or does not support groups ex. exchange) 57 | 58 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 59 | 60 | //add from vCard (iOS5+) (pre iOS5 these methods are no-ops) 61 | -(NSArray*)addPeopleFromVCardRepresentation:(NSData*)representation; //returns an array of RHPerson objects 62 | 63 | -(NSData*)vCardRepresentationForPeople; 64 | 65 | #endif //end iOS5+ 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /RHAddressBook/RHGroup.h: -------------------------------------------------------------------------------- 1 | // 2 | // RHGroup.h 3 | // RHAddressBook 4 | // 5 | // Created by Richard Heard on 14/11/11. 6 | // Copyright (c) 2011 Richard Heard. All rights reserved. 7 | // 8 | // Redistribution and use in source and binary forms, with or without 9 | // modification, are permitted provided that the following conditions 10 | // are met: 11 | // 1. Redistributions of source code must retain the above copyright 12 | // notice, this list of conditions and the following disclaimer. 13 | // 2. Redistributions in binary form must reproduce the above copyright 14 | // notice, this list of conditions and the following disclaimer in the 15 | // documentation and/or other materials provided with the distribution. 16 | // 3. The name of the author may not be used to endorse or promote products 17 | // derived from this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | // 30 | 31 | #import "RHRecord.h" 32 | 33 | @class RHPerson; 34 | @class RHSource; 35 | @class CLLocation; 36 | 37 | //To create a new empty instance of a group use -[RHAddressBook createGroup] or -[RHSource createGroup] 38 | @interface RHGroup : RHRecord 39 | 40 | //once a group object is created using a given source object from an ab instance, its not safe to use that object with any other instance of the addressbook 41 | +(instancetype)newGroupInSource:(RHSource*)source; 42 | 43 | //properties 44 | @property (copy) NSString *name; 45 | @property (retain, readonly) RHSource *source; 46 | @property (readonly) NSInteger count; 47 | 48 | //add and remove members from this group 49 | -(BOOL)addMember:(RHPerson*)person; 50 | -(BOOL)removeMember:(RHPerson*)person; 51 | -(void)removeAllMembers; 52 | 53 | //access group members 54 | @property (nonatomic, readonly, copy) NSArray *members; 55 | -(NSArray*)membersOrderedBySortOrdering:(ABPersonSortOrdering)ordering; 56 | @property (nonatomic, readonly, copy) NSArray *membersOrderedByFirstName; 57 | @property (nonatomic, readonly, copy) NSArray *membersOrderedByLastName; 58 | @property (nonatomic, readonly, copy) NSArray *membersOrderedByUsersPreference; 59 | 60 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 61 | //vCard (iOS5+) pre iOS5 this method is a no-op 62 | -(NSData*)vCardRepresentationForMembers; 63 | 64 | #if RH_AB_INCLUDE_GEOCODING 65 | //geolocation 66 | -(NSArray*)membersWithinDistance:(double)distance ofLocation:(CLLocation*)location; 67 | #endif // Geocoding 68 | 69 | #endif //end iOS5+ 70 | 71 | //remove group from addressBook 72 | -(BOOL)remove; 73 | 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /RHAddressBook/NSThread+RHBlockAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSThread+RHBlockAdditions.m 3 | // RHAddressBook 4 | // 5 | // Created by Richard Heard on 22/8/11. 6 | // Copyright (c) 2011 Richard Heard. All rights reserved. 7 | // 8 | // Redistribution and use in source and binary forms, with or without 9 | // modification, are permitted provided that the following conditions 10 | // are met: 11 | // 1. Redistributions of source code must retain the above copyright 12 | // notice, this list of conditions and the following disclaimer. 13 | // 2. Redistributions in binary form must reproduce the above copyright 14 | // notice, this list of conditions and the following disclaimer in the 15 | // documentation and/or other materials provided with the distribution. 16 | // 3. The name of the author may not be used to endorse or promote products 17 | // derived from this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | // 30 | 31 | #import "NSThread+RHBlockAdditions.h" 32 | 33 | @implementation NSThread (RHBlockAdditions) 34 | 35 | #pragma mark - public 36 | -(void)rh_performBlock:(VoidBlock)block{ 37 | [self rh_performBlock:block waitUntilDone:YES]; 38 | } 39 | 40 | -(void)rh_performBlock:(VoidBlock)block waitUntilDone:(BOOL)wait{ 41 | //if current thread and wait (run directly) 42 | if ([[NSThread currentThread] isEqual:self] && wait){ 43 | block(); return; 44 | } 45 | [self performSelector:@selector(_rh_runBlock:) onThread:self withObject:arc_autorelease([block copy]) waitUntilDone:wait]; 46 | } 47 | 48 | -(void)rh_performBlock:(VoidBlock)block afterDelay:(NSTimeInterval)delay{ 49 | [self performSelector:@selector(rh_performBlock:) withObject:arc_autorelease([block copy]) afterDelay:delay]; 50 | } 51 | 52 | 53 | #pragma mark - helpers 54 | +(void)rh_performBlockOnMainThread:(VoidBlock)block{ 55 | [[NSThread mainThread] rh_performBlock:block]; 56 | } 57 | 58 | +(void)rh_performBlockOnMainThread:(VoidBlock)block waitUntilDone:(BOOL)wait{ 59 | [[NSThread mainThread] rh_performBlock:block waitUntilDone:wait]; 60 | } 61 | 62 | +(void)rh_performBlockInBackground:(VoidBlock)block{ 63 | [NSThread performSelectorInBackground:@selector(_rh_runBlock:) withObject:arc_autorelease([block copy])]; 64 | } 65 | 66 | -(void)_rh_runBlock:(void (^)())block{ 67 | if (block) block(); 68 | } 69 | 70 | @end 71 | 72 | 73 | //include an implementation in this file so we don't have to use -load_all for this category to be included in a static lib 74 | @interface RHFixCategoryBugClassRHBA : NSObject @end @implementation RHFixCategoryBugClassRHBA @end 75 | 76 | -------------------------------------------------------------------------------- /RHAddressBook/RHRecord.h: -------------------------------------------------------------------------------- 1 | // 2 | // RHRecord.h 3 | // RHAddressBook 4 | // 5 | // Created by Richard Heard on 11/11/11. 6 | // Copyright (c) 2011 Richard Heard. All rights reserved. 7 | // 8 | // Redistribution and use in source and binary forms, with or without 9 | // modification, are permitted provided that the following conditions 10 | // are met: 11 | // 1. Redistributions of source code must retain the above copyright 12 | // notice, this list of conditions and the following disclaimer. 13 | // 2. Redistributions in binary form must reproduce the above copyright 14 | // notice, this list of conditions and the following disclaimer in the 15 | // documentation and/or other materials provided with the distribution. 16 | // 3. The name of the author may not be used to endorse or promote products 17 | // derived from this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | // 30 | 31 | #import 32 | #import 33 | 34 | @class RHAddressBook; 35 | @class RHMultiValue; 36 | 37 | @interface RHRecord : NSObject{ 38 | ABRecordID _recordID; 39 | __strong RHAddressBook *_addressBook; //strong, we don't want our addressbook instance going away while we are still alive. ever. 40 | ABRecordRef _recordRef; 41 | } 42 | 43 | //thread safe access block 44 | -(void)performRecordAction:(void (^)(ABRecordRef recordRef))actionBlock waitUntilDone:(BOOL)wait; 45 | 46 | //accessors 47 | @property (retain, readonly) RHAddressBook* addressBook; // address book instance that this record is a member of 48 | 49 | @property (readonly) ABRecordID recordID; 50 | @property (readonly) ABRecordRef recordRef; 51 | @property (readonly) ABRecordType recordType; 52 | @property (copy, readonly) NSString *compositeName; 53 | 54 | //generic property accessors (only safe for toll free bridged values) 55 | -(id)getBasicValueForPropertyID:(ABPropertyID)propertyID; 56 | -(BOOL)setBasicValue:(CFTypeRef)value forPropertyID:(ABPropertyID)propertyID error:(NSError**)error; 57 | -(BOOL)unsetBasicValueForPropertyID:(ABPropertyID)propertyID error:(NSError**)error; 58 | 59 | 60 | //multi value accessors 61 | -(RHMultiValue*)getMultiValueForPropertyID:(ABPropertyID)propertyID; //returned multi's are always immutable, if you want to edit use -[RHMultiValue mutableCopy] 62 | -(BOOL)setMultiValue:(RHMultiValue*)multiValue forPropertyID:(ABPropertyID)propertyID error:(NSError**)error; 63 | -(BOOL)unsetMultiValueForPropertyID:(ABPropertyID)propertyID error:(NSError**)error; 64 | 65 | 66 | 67 | //save (convenience methods.. these just forward up to this records addressbook) 68 | -(BOOL)save; 69 | -(BOOL)saveWithError:(NSError**)error; 70 | @property (nonatomic, readonly) BOOL hasUnsavedChanges; //addressbook level, not record level 71 | -(void)revert; 72 | 73 | //misc 74 | +(NSString*)descriptionForRecordType:(ABRecordType)type; 75 | +(NSString*)descriptionForPropertyType:(ABRecordType)type; 76 | 77 | @end 78 | -------------------------------------------------------------------------------- /RHAddressBookLogicTests/UIImage+RHResizingAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+RHResizingAdditions.m 3 | // RHKit 4 | // 5 | // Created by Richard Heard on 18/03/12. 6 | // Copyright (c) 2012 Richard Heard. All rights reserved. 7 | // 8 | // Redistribution and use in source and binary forms, with or without 9 | // modification, are permitted provided that the following conditions 10 | // are met: 11 | // 1. Redistributions of source code must retain the above copyright 12 | // notice, this list of conditions and the following disclaimer. 13 | // 2. Redistributions in binary form must reproduce the above copyright 14 | // notice, this list of conditions and the following disclaimer in the 15 | // documentation and/or other materials provided with the distribution. 16 | // 3. The name of the author may not be used to endorse or promote products 17 | // derived from this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | // 30 | 31 | #import "UIImage+RHResizingAdditions.h" 32 | 33 | @implementation UIImage (RHResizingAdditions) 34 | 35 | 36 | -(UIImage *)imageResizedToSize:(CGSize)size{ 37 | return UIImageResizeImageToSize(self, size); 38 | } 39 | 40 | @end 41 | 42 | 43 | 44 | #pragma mark - underlying implementation 45 | 46 | UIImage * UIImageResizeImageToSize(UIImage *image, CGSize size){ 47 | 48 | CGImageRef imageRef = image.CGImage; 49 | if (!imageRef) return nil; //unsupported 50 | 51 | CGContextRef context = CGBitmapContextCreate(NULL, size.width, size.height, CGImageGetBitsPerComponent(imageRef), 0, CGImageGetColorSpace(imageRef), CGImageGetBitmapInfo(imageRef)); 52 | if (! context) { 53 | //likely an image in a unsupported bitmap parameter combination, try again with a standard set 54 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 55 | context = CGBitmapContextCreate(NULL, size.width, size.height, 8, 0, colorSpace, (CGBitmapInfo)kCGImageAlphaPremultipliedLast); 56 | CGColorSpaceRelease(colorSpace); 57 | } 58 | 59 | if (!context) return nil; //if that also fails, bail 60 | 61 | //high quality 62 | CGContextSetInterpolationQuality(context, kCGInterpolationHigh); 63 | 64 | CGContextDrawImage(context, CGRectMake(0, 0, size.width, size.height), image.CGImage); 65 | 66 | CGImageRef resizedImageRef = CGBitmapContextCreateImage(context); 67 | 68 | //apply the same attributes to the new UIImage. (ie we resize the CGImage as is, then let the new image know if it needs to translate it for display etc.) 69 | UIImage *resizedImage = nil; 70 | if (resizedImageRef){ 71 | resizedImage = [UIImage imageWithCGImage:resizedImageRef scale:image.scale orientation:image.imageOrientation]; 72 | CGImageRelease(resizedImageRef); 73 | } 74 | 75 | CGContextRelease(context); 76 | 77 | return resizedImage; 78 | } 79 | 80 | //include an implementation in this file so we don't have to use -load_all for this category to be included in a static lib 81 | @interface RHFixCategoryBugClassRHRA : NSObject @end @implementation RHFixCategoryBugClassRHRA @end 82 | -------------------------------------------------------------------------------- /RHAddressBook/RHARCSupport.h: -------------------------------------------------------------------------------- 1 | // 2 | // RHARCSupport.h 3 | // 4 | // Created by Richard Heard on 3/07/12. 5 | // Copyright (c) 2012 Richard Heard. All rights reserved. 6 | // 7 | // Redistribution and use in source and binary forms, with or without 8 | // modification, are permitted provided that the following conditions 9 | // are met: 10 | // 1. Redistributions of source code must retain the above copyright 11 | // notice, this list of conditions and the following disclaimer. 12 | // 2. Redistributions in binary form must reproduce the above copyright 13 | // notice, this list of conditions and the following disclaimer in the 14 | // documentation and/or other materials provided with the distribution. 15 | // 3. The name of the author may not be used to endorse or promote products 16 | // derived from this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 19 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 21 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 23 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | // 29 | // supporting macros for code to allow building with and without arc 30 | 31 | 32 | #ifndef __has_feature 33 | // not LLVM Compiler 34 | #define __has_feature(x) 0 35 | #endif 36 | 37 | #ifndef CF_CONSUMED 38 | #if __has_feature(attribute_cf_consumed) 39 | #define CF_CONSUMED __attribute__((cf_consumed)) 40 | #else 41 | #define CF_CONSUMED 42 | #endif 43 | #endif 44 | 45 | 46 | // ----- ARC Enabled ----- 47 | #if __has_feature(objc_arc) && !defined(ARC_IS_ENABLED) 48 | 49 | #define ARC_IS_ENABLED 1 50 | 51 | //define retain count macro wrappers 52 | #define arc_retain(x) (x) 53 | #define arc_release(x) 54 | #define arc_release_nil(x) (x = nil) 55 | #define arc_autorelease(x) (x) 56 | #define arc_super_dealloc() 57 | 58 | //add CF bridging methods 59 | #define ARCBridgingRetain(x) CFBridgingRetain(x) 60 | #define ARCBridgingRelease(x) CFBridgingRelease(x) 61 | 62 | #endif 63 | 64 | 65 | // ----- ARC Disabled ----- 66 | #if !__has_feature(objc_arc) && !defined(ARC_IS_ENABLED) 67 | 68 | #define ARC_IS_ENABLED 0 69 | 70 | //define retain count macro wrappers 71 | #define arc_retain(x) ([x retain]) 72 | #define arc_release(x) ([x release]) 73 | #define arc_release_nil(x) [x release]; x = nil; 74 | #define arc_autorelease(x) ([x autorelease]) 75 | #define arc_super_dealloc() ([super dealloc]) 76 | 77 | //add arc keywords if not already defined 78 | #ifndef __bridge 79 | #define __bridge 80 | #endif 81 | #ifndef __bridge_retained 82 | #define __bridge_retained 83 | #endif 84 | #ifndef __bridge_transfer 85 | #define __bridge_transfer 86 | #endif 87 | 88 | #ifndef __autoreleasing 89 | #define __autoreleasing 90 | #endif 91 | #ifndef __strong 92 | #define __strong 93 | #endif 94 | #ifndef __weak 95 | #define __weak 96 | #endif 97 | #ifndef __unsafe_unretained 98 | #define __unsafe_unretained 99 | #endif 100 | 101 | //add CF bridging methods (we inline these ourselves because they are not included in older sdks) 102 | NS_INLINE CF_RETURNS_RETAINED CFTypeRef ARCBridgingRetain(id X) { 103 | return X ? CFRetain((CFTypeRef)X) : NULL; 104 | } 105 | 106 | NS_INLINE id ARCBridgingRelease(CFTypeRef CF_CONSUMED X) { 107 | return [(id)CFMakeCollectable(X) autorelease]; 108 | } 109 | 110 | #endif 111 | 112 | 113 | //if clarity helper 114 | #define ARC_IS_NOT_ENABLED (!(ARC_IS_ENABLED)) 115 | 116 | 117 | -------------------------------------------------------------------------------- /RHAddressBook/RHMultiValue.h: -------------------------------------------------------------------------------- 1 | // 2 | // RHMultiValue.h 3 | // RHAddressBook 4 | // 5 | // Created by Richard Heard on 11/11/11. 6 | // Copyright (c) 2011 Richard Heard. All rights reserved. 7 | // 8 | // Redistribution and use in source and binary forms, with or without 9 | // modification, are permitted provided that the following conditions 10 | // are met: 11 | // 1. Redistributions of source code must retain the above copyright 12 | // notice, this list of conditions and the following disclaimer. 13 | // 2. Redistributions in binary form must reproduce the above copyright 14 | // notice, this list of conditions and the following disclaimer in the 15 | // documentation and/or other materials provided with the distribution. 16 | // 3. The name of the author may not be used to endorse or promote products 17 | // derived from this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | // 30 | 31 | #import 32 | #import 33 | #import "RHAddressBook.h" 34 | 35 | @class RHMultiValue; 36 | @class RHMutableMultiValue; 37 | 38 | //some clarifying typedefs. no need for separate subclasses. 39 | typedef RHMultiValue RHMultiStringValue; 40 | typedef RHMultiValue RHMultiIntegerValue; 41 | typedef RHMultiValue RHMultiRealValue; 42 | typedef RHMultiValue RHMultiDateTimeValue; 43 | typedef RHMultiValue RHMultiDictionaryValue; 44 | 45 | typedef RHMutableMultiValue RHMutableMultiStringValue; 46 | typedef RHMutableMultiValue RHMutableMultiIntegerValue; 47 | typedef RHMutableMultiValue RHMutableMultiRealValue; 48 | typedef RHMutableMultiValue RHMutableMultiDateTimeValue; 49 | typedef RHMutableMultiValue RHMutableMultiDictionaryValue; 50 | 51 | 52 | @interface RHMultiValue : NSObject { 53 | ABMultiValueRef _multiValueRef; 54 | } 55 | 56 | // a multi-value is an ordered collection of key / value pairs. (mutable or immutable.) 57 | // this is a generic top level collection object. 58 | 59 | @property (readonly) ABMultiValueRef multiValueRef; 60 | 61 | //init 62 | -(instancetype)initWithMultiValueRef:(ABMultiValueRef)multiValueRef NS_DESIGNATED_INITIALIZER; //passing NULL to init is invalid 63 | 64 | //accessors 65 | @property (nonatomic, readonly) ABPropertyType propertyType; 66 | 67 | //values 68 | @property (nonatomic, readonly) NSUInteger count; 69 | -(id)valueAtIndex:(NSUInteger)index; 70 | @property (nonatomic, readonly, copy) NSArray *values; 71 | 72 | //labels 73 | -(NSString*)labelAtIndex:(NSUInteger)index; 74 | -(NSString*)localizedLabelAtIndex:(NSUInteger)index; 75 | 76 | //identifier 77 | -(NSUInteger)indexForIdentifier:(ABMultiValueIdentifier)identifier; 78 | -(ABMultiValueIdentifier)identifierAtIndex:(NSUInteger)index; 79 | 80 | //convenience accessor 81 | -(NSUInteger)firstIndexOfValue:(id)value; 82 | 83 | //mutable copy 84 | -(RHMutableMultiValue*)mutableCopy; 85 | 86 | //equality 87 | -(BOOL)isEqualToMultiValue:(RHMultiValue*)otherMultiValue; 88 | 89 | @end 90 | 91 | 92 | //mutable additions 93 | @interface RHMutableMultiValue : RHMultiValue 94 | 95 | //init 96 | -(instancetype)initWithType:(ABPropertyType)newPropertyType; //a new MultiValue Ref of specified type is created on your behalf. 97 | 98 | -(ABMultiValueIdentifier)addValue:(id)value withLabel:(NSString *)label; //on failure kABMultiValueInvalidIdentifier 99 | -(ABMultiValueIdentifier)insertValue:(id)value withLabel:(NSString *)label atIndex:(NSUInteger)index; //on failure kABMultiValueInvalidIdentifier 100 | 101 | -(BOOL)removeValueAndLabelAtIndex:(NSUInteger)index; 102 | 103 | -(BOOL)replaceValueAtIndex:(NSUInteger)index withValue:(id)value; 104 | -(BOOL)replaceLabelAtIndex:(NSUInteger)index withLabel:(NSString*)label; 105 | 106 | 107 | @end 108 | -------------------------------------------------------------------------------- /RHAddressBookLogicTests/UIImage+RHPixelAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+RHPixelAdditions.m 3 | // RHKit 4 | // 5 | // Created by Richard Heard on 18/03/12. 6 | // Copyright (c) 2012 Richard Heard. All rights reserved. 7 | // 8 | // Redistribution and use in source and binary forms, with or without 9 | // modification, are permitted provided that the following conditions 10 | // are met: 11 | // 1. Redistributions of source code must retain the above copyright 12 | // notice, this list of conditions and the following disclaimer. 13 | // 2. Redistributions in binary form must reproduce the above copyright 14 | // notice, this list of conditions and the following disclaimer in the 15 | // documentation and/or other materials provided with the distribution. 16 | // 3. The name of the author may not be used to endorse or promote products 17 | // derived from this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | // 30 | 31 | #import "UIImage+RHPixelAdditions.h" 32 | 33 | @implementation UIImage (RHPixelAdditions) 34 | 35 | -(NSData *)rgba{ 36 | return UIImageGetRGBAForImage(self); 37 | } 38 | -(NSData *)rgbaForPoint:(CGPoint)point{ 39 | return UIImageGetRGBAForPointInImage(point, self); 40 | } 41 | 42 | @end 43 | 44 | 45 | #pragma mark - underlying implementation 46 | 47 | pixel * UIImageCopyRGBAForImage(UIImage *image){ 48 | 49 | if (!image) return NULL; 50 | 51 | CGFloat width = image.size.width; 52 | CGFloat height = image.size.height; 53 | size_t bitsPerComponent = 8; 54 | size_t bytesPerPixel = 4; 55 | size_t bytesPerRow = bytesPerPixel * width; 56 | 57 | pixel *pixels = calloc(width * height, sizeof(pixel)); 58 | 59 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 60 | CGContextRef context = CGBitmapContextCreate(pixels, width, height, bitsPerComponent, bytesPerRow, colorSpace, (CGBitmapInfo)kCGImageAlphaPremultipliedLast); 61 | 62 | if (context) { 63 | UIGraphicsPushContext(context); 64 | [image drawAtPoint:CGPointMake(0,0)]; 65 | UIGraphicsPopContext(); 66 | } 67 | 68 | CGColorSpaceRelease(colorSpace); 69 | CGContextRelease(context); 70 | 71 | return pixels; 72 | } 73 | 74 | pixel * UIImageCopyRGBAForPointInImage(CGPoint point, UIImage*image){ 75 | 76 | if (!image) return NULL; 77 | 78 | CGFloat width = 1.0f; 79 | CGFloat height = 1.0f; 80 | size_t bitsPerComponent = 8; 81 | size_t bytesPerPixel = 4; 82 | size_t bytesPerRow = bytesPerPixel * width; 83 | 84 | pixel *pixels = calloc(width * height, sizeof(pixel)); 85 | 86 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 87 | CGContextRef context = CGBitmapContextCreate(pixels, width, height, bitsPerComponent, bytesPerRow, colorSpace, (CGBitmapInfo)kCGImageAlphaPremultipliedLast); 88 | 89 | if (context){ 90 | UIGraphicsPushContext(context); 91 | [image drawAtPoint:CGPointMake(-point.x, -point.y)]; 92 | UIGraphicsPopContext(); 93 | } 94 | CGColorSpaceRelease(colorSpace); 95 | CGContextRelease(context); 96 | 97 | return pixels; 98 | } 99 | 100 | 101 | NSData * UIImageGetRGBAForImage(UIImage* image){ 102 | pixel *pixels = UIImageCopyRGBAForImage(image); 103 | if (!pixels) return nil; 104 | 105 | NSData *data = [NSData dataWithBytes:pixels length:(4 * image.size.width * image.size.height)]; 106 | free(pixels); 107 | 108 | return data; 109 | } 110 | 111 | NSData * UIImageGetRGBAForPointInImage(CGPoint point, UIImage* image){ 112 | pixel *pixels = UIImageCopyRGBAForPointInImage(point, image); 113 | if (!pixels) return nil; 114 | 115 | NSData *data = [NSData dataWithBytes:pixels length:(4 * 1 * 1)]; 116 | free(pixels); 117 | 118 | return data; 119 | } 120 | 121 | //include an implementation in this file so we don't have to use -load_all for this category to be included in a static lib 122 | @interface RHFixCategoryBugClassRHPA : NSObject @end @implementation RHFixCategoryBugClassRHPA @end 123 | -------------------------------------------------------------------------------- /RHAddressBook/RHSource.m: -------------------------------------------------------------------------------- 1 | // 2 | // RHSource.m 3 | // RHAddressBook 4 | // 5 | // Created by Richard Heard on 11/11/11. 6 | // Copyright (c) 2011 Richard Heard. All rights reserved. 7 | // 8 | // Redistribution and use in source and binary forms, with or without 9 | // modification, are permitted provided that the following conditions 10 | // are met: 11 | // 1. Redistributions of source code must retain the above copyright 12 | // notice, this list of conditions and the following disclaimer. 13 | // 2. Redistributions in binary form must reproduce the above copyright 14 | // notice, this list of conditions and the following disclaimer in the 15 | // documentation and/or other materials provided with the distribution. 16 | // 3. The name of the author may not be used to endorse or promote products 17 | // derived from this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | // 30 | 31 | #import "RHSource.h" 32 | 33 | #import "RHAddressBook.h" 34 | #import "NSThread+RHBlockAdditions.h" 35 | 36 | @implementation RHSource 37 | 38 | -(NSString*)name{ 39 | NSString *sourceName = [self getBasicValueForPropertyID:kABSourceNameProperty]; 40 | return sourceName; 41 | } 42 | 43 | -(ABSourceType)type{ 44 | NSNumber *sourceType = [self getBasicValueForPropertyID:kABSourceTypeProperty]; 45 | return [sourceType intValue]; 46 | } 47 | 48 | //groups 49 | -(NSArray*)groups{ 50 | return [_addressBook groupsInSource:self]; 51 | } 52 | 53 | 54 | //people 55 | -(NSArray*)people{ 56 | 57 | NSMutableArray *people = [NSMutableArray array]; 58 | 59 | [_addressBook performAddressBookAction:^(ABAddressBookRef addressBookRef) { 60 | 61 | CFArrayRef peopleRefs = ABAddressBookCopyArrayOfAllPeopleInSource(addressBookRef, _recordRef); 62 | 63 | if (peopleRefs){ 64 | for (CFIndex i = 0; i < CFArrayGetCount(peopleRefs); i++) { 65 | ABRecordRef personRef = CFArrayGetValueAtIndex(peopleRefs, i); 66 | RHPerson *person = [_addressBook personForABRecordRef:personRef]; // this method either pulls from the old cache or creates a new object 67 | if (person)[people addObject:person]; 68 | } 69 | 70 | CFRelease(peopleRefs); 71 | } 72 | } waitUntilDone:YES]; 73 | 74 | return [NSArray arrayWithArray:people]; 75 | } 76 | 77 | -(NSArray*)peopleOrderedBySortOrdering:(ABPersonSortOrdering)ordering{ 78 | NSMutableArray *people = [NSMutableArray array]; 79 | 80 | [_addressBook performAddressBookAction:^(ABAddressBookRef addressBookRef) { 81 | 82 | CFArrayRef peopleRefs = ABAddressBookCopyArrayOfAllPeopleInSourceWithSortOrdering(addressBookRef, _recordRef, ordering); 83 | 84 | if (peopleRefs){ 85 | for (CFIndex i = 0; i < CFArrayGetCount(peopleRefs); i++) { 86 | ABRecordRef personRef = CFArrayGetValueAtIndex(peopleRefs, i); 87 | RHPerson *person = [_addressBook personForABRecordRef:personRef]; // this method either pulls from the old cache or creates a new object 88 | if (person)[people addObject:person]; 89 | } 90 | 91 | CFRelease(peopleRefs); 92 | } 93 | } waitUntilDone:YES]; 94 | 95 | return [NSArray arrayWithArray:people]; 96 | } 97 | 98 | -(NSArray*)peopleOrderedByFirstName{ 99 | return [self peopleOrderedBySortOrdering:kABPersonSortByFirstName]; 100 | } 101 | 102 | -(NSArray*)peopleOrderedByLastName{ 103 | return [self peopleOrderedBySortOrdering:kABPersonSortByLastName]; 104 | } 105 | 106 | -(NSArray*)peopleOrderedByUsersPreference{ 107 | return [self peopleOrderedBySortOrdering:[RHAddressBook sortOrdering]]; 108 | } 109 | 110 | 111 | //additions 112 | -(RHPerson*)newPerson{ 113 | return [_addressBook newPersonInSource:self]; 114 | } 115 | 116 | -(RHGroup*)newGroup{ 117 | return [_addressBook newGroupInSource:self]; 118 | } 119 | 120 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 121 | #pragma mark - vCard (iOS5+) 122 | -(NSArray*)addPeopleFromVCardRepresentation:(NSData*)representation{ 123 | return [_addressBook addPeopleFromVCardRepresentation:representation toSource:self]; 124 | } 125 | 126 | -(NSData*)vCardRepresentationForPeople{ 127 | return [_addressBook vCardRepresentationForPeople:[self people]]; 128 | } 129 | 130 | #endif //end iOS5+ 131 | 132 | 133 | -(NSString*)description{ 134 | return [NSString stringWithFormat:@"<%@: %p> name:%@ type:%i", NSStringFromClass([self class]), self, self.name, self.type]; 135 | } 136 | 137 | 138 | @end 139 | -------------------------------------------------------------------------------- /RHAddressBookLogicTests/UIImage+RHComparingAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+RHComparingAdditions.m 3 | // RHKit 4 | // 5 | // Created by Richard Heard on 18/03/12. 6 | // Copyright (c) 2012 Richard Heard. All rights reserved. 7 | // 8 | // Redistribution and use in source and binary forms, with or without 9 | // modification, are permitted provided that the following conditions 10 | // are met: 11 | // 1. Redistributions of source code must retain the above copyright 12 | // notice, this list of conditions and the following disclaimer. 13 | // 2. Redistributions in binary form must reproduce the above copyright 14 | // notice, this list of conditions and the following disclaimer in the 15 | // documentation and/or other materials provided with the distribution. 16 | // 3. The name of the author may not be used to endorse or promote products 17 | // derived from this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | // 30 | 31 | #import "UIImage+RHComparingAdditions.h" 32 | 33 | @implementation UIImage (RHComparingAdditions) 34 | 35 | 36 | -(CGFloat)percentageDifferenceBetweenImage:(UIImage *)image{ 37 | return UIImagePercentageDifferenceBetweenImages(self, image); 38 | } 39 | 40 | -(CGFloat)percentageDifferenceBetweenImage:(UIImage *)image withTolerance:(CGFloat)percentageTolerance{ 41 | return UIImagePercentageDifferenceBetweenImagesWithTolerance(self, image, percentageTolerance, NO); 42 | } 43 | 44 | -(CGFloat)percentageDifferenceBetweenImage:(UIImage *)image withTolerance:(CGFloat)percentageTolerance andScaleIfImageSizesMismatched:(BOOL)shouldScale{ 45 | return UIImagePercentageDifferenceBetweenImagesWithTolerance(self, image, percentageTolerance, shouldScale); 46 | } 47 | 48 | @end 49 | 50 | 51 | #pragma mark - underlying implementation 52 | 53 | CGFloat UIImagePercentageDifferenceBetweenImages(UIImage* image1, UIImage* image2){ 54 | return UIImagePercentageDifferenceBetweenImagesWithTolerance(image1, image2, 0.1f, NO); 55 | } 56 | 57 | CGFloat UIImagePercentageDifferenceBetweenImagesWithTolerance(UIImage* image1, UIImage* image2, CGFloat percentageTolerance, BOOL shouldScaleIfImageSizesMismatched){ 58 | 59 | //make sure we have 2 images 60 | if (!image1) return 1.0f; 61 | if (!image2) return 1.0f; 62 | int tolerance = MAX(0.0f, MIN(1.0f, percentageTolerance)) * 255; 63 | 64 | float width = image1.size.width; 65 | float height = image1.size.height; 66 | 67 | if (width != image2.size.width || height != image2.size.height){ 68 | 69 | if (shouldScaleIfImageSizesMismatched){ 70 | //scale the larger image to match the size of the smaller image 71 | if ((image1.size.width * image1.size.height) > (image2.size.width * image2.size.height)){ 72 | image1 = [image1 imageResizedToSize:image2.size]; 73 | } else { 74 | image2 = [image2 imageResizedToSize:image1.size]; 75 | } 76 | 77 | if (!image1 || !image2){ 78 | NSLog(@"Error: Failed to scale an image for comparison."); 79 | return 1.0f; 80 | } 81 | 82 | //reset 83 | width = image1.size.width; 84 | height = image1.size.height; 85 | 86 | } else { 87 | return 1.0f; //maximum difference 88 | } 89 | } 90 | 91 | 92 | pixel *image1Pixels = UIImageCopyRGBAForImage(image1); 93 | pixel *image2Pixels = UIImageCopyRGBAForImage(image2); 94 | 95 | unsigned long detectedDifferences = 0; 96 | unsigned long totalPixelCount = width * height; 97 | 98 | //we will mutate the pointers, so make a copy 99 | pixel *image1Ptr = image1Pixels; 100 | pixel *image2Ptr = image2Pixels; 101 | for (unsigned long index = 0; index < totalPixelCount; index++) { 102 | 103 | 104 | if (abs(image1Ptr->R - image2Ptr->R) > tolerance || abs(image1Ptr->G - image2Ptr->G) > tolerance || abs(image1Ptr->B - image2Ptr->B) > tolerance || abs(image1Ptr->A - image2Ptr->A) > tolerance) { 105 | //one or more pixel components differs by tolerance or more 106 | detectedDifferences++; 107 | } 108 | 109 | //increment pointers 110 | image1Ptr++; 111 | image2Ptr++; 112 | 113 | } 114 | 115 | free(image1Pixels); 116 | free(image2Pixels); 117 | 118 | return (float)detectedDifferences / (float)totalPixelCount; 119 | 120 | } 121 | 122 | 123 | //include an implementation in this file so we don't have to use -load_all for this category to be included in a static lib 124 | @interface RHFixCategoryBugClassRHCA : NSObject @end @implementation RHFixCategoryBugClassRHCA @end 125 | -------------------------------------------------------------------------------- /RHAddressBookLogicTests/RHAddressBookLogicTests.h: -------------------------------------------------------------------------------- 1 | // 2 | // RHAddressBookLogicTests.h 3 | // RHAddressBookLogicTests 4 | // 5 | // Created by Richard Heard on 13/11/11. 6 | // Copyright (c) 2011 Richard Heard. All rights reserved. 7 | // 8 | // Redistribution and use in source and binary forms, with or without 9 | // modification, are permitted provided that the following conditions 10 | // are met: 11 | // 1. Redistributions of source code must retain the above copyright 12 | // notice, this list of conditions and the following disclaimer. 13 | // 2. Redistributions in binary form must reproduce the above copyright 14 | // notice, this list of conditions and the following disclaimer in the 15 | // documentation and/or other materials provided with the distribution. 16 | // 3. The name of the author may not be used to endorse or promote products 17 | // derived from this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | // 30 | 31 | #import 32 | #import 33 | #import "AddressBook.h" 34 | #import "UIImage+RHComparingAdditions.h" 35 | #import "RHARCSupport.h" 36 | 37 | @interface RHAddressBookLogicTests : XCTestCase { 38 | RHAddressBook *_ab; 39 | } 40 | 41 | -(void)setUp; 42 | -(void)tearDown; 43 | 44 | #pragma mark - addressbook 45 | -(void)testSaving; 46 | -(void)testReverting; 47 | -(void)testUnsavedChanges; 48 | -(void)testForRecordRefMethods; 49 | -(void)testUserPrefs; //sorting & display 50 | -(void)testGroupsAndPeopleFromAnotherAddressBook; 51 | -(void)testPassingNilToPublicMethods; 52 | 53 | #pragma mark - sources 54 | -(void)testSources; 55 | 56 | #pragma mark - groups 57 | -(void)testGroups; //create and delete. (in each source available.) 58 | -(void)testGroupProperties; 59 | -(void)testGroupForABRecordRefMethod; 60 | 61 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 62 | #pragma mark - vcards 63 | -(void)testVCardSingleExport; //test both methods on RHAddressBook and on RHPerson. 64 | -(void)testVCardMultipleExport; 65 | -(void)testVCardSingleImport; 66 | -(void)testVCardMultipleImport; 67 | #endif //end iOS5+ 68 | 69 | #pragma mark - people 70 | -(void)testPeople; //create and delete 71 | -(void)testPeopleWithName; 72 | -(void)testPersonProperties; 73 | -(void)testPersonLocalization; 74 | -(void)testPersonImage; 75 | -(void)testLinkedPeople; 76 | #if RH_AB_INCLUDE_GEOCODING 77 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 78 | -(void)testPersonGeocoding; 79 | #endif //end iOS5+ 80 | #endif //end Geocoding 81 | -(void)testPersonForABRecordRefMethod; 82 | 83 | 84 | #if RH_AB_INCLUDE_GEOCODING 85 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 86 | #pragma mark - location services 87 | -(void)testGeocoding; 88 | #endif //end iOS5+ 89 | #endif //end Geocoding 90 | 91 | #pragma mark - misc tests 92 | -(void)testAddingPersonToGroupFromOtherAddressBook; 93 | -(void)testWeakLinkedCache; 94 | -(void)testWeakLinkedRefMap; 95 | #if ARC_IS_NOT_ENABLED 96 | -(void)testWeakLinkedCacheConcurrency; 97 | #endif 98 | 99 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 100 | #pragma mark - running on pre iOS5+ sanity 101 | -(void)testCallingPostFiveAvailableMethodsOnPreFiveDevices; 102 | #endif //end iOS5+ 103 | 104 | //helpers 105 | -(void)populateObject:(id)object UsingDictionary:(NSDictionary*)dictionary; 106 | -(void)validateObject:(id)object UsingDictionary:(NSDictionary*)dictionary; 107 | -(void)populateAndValidateObject:(id)object UsingDictionary:(NSDictionary*)dictionary; 108 | 109 | -(UIImage*)imageNamed:(NSString*)name; 110 | 111 | //generators 112 | -(NSDictionary*)randomPersonDictionary; 113 | 114 | -(NSString*)randomString; 115 | -(NSDate*)randomDate; 116 | 117 | -(RHMultiStringValue*)randomMultiString; 118 | -(RHMultiDateTimeValue*)randomMultiDateTime; 119 | 120 | -(RHMultiDictionaryValue*)randomMultiDictionary; 121 | -(RHMultiDictionaryValue*)randomMultiAddressDictionary; 122 | 123 | -(id)ivar:(NSString*)ivarName forObject:(id)object; 124 | 125 | //system versioning checks 126 | #define SYSTEM_VERSION_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame) 127 | #define SYSTEM_VERSION_GREATER_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending) 128 | #define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending) 129 | #define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending) 130 | #define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending) 131 | 132 | 133 | @end 134 | -------------------------------------------------------------------------------- /RHAddressBookTester/RHAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // RHAppDelegate.m 3 | // RHAddressBookTester 4 | // 5 | // Created by Richard Heard on 13/11/11. 6 | // Copyright (c) 2011 Richard Heard. All rights reserved. 7 | // 8 | 9 | 10 | #define PERF_TEST_SETUP 0 11 | #define PERF_TEST 1 12 | 13 | #import "RHAppDelegate.h" 14 | 15 | #import "RHAddressBookViewController.h" 16 | 17 | #import 18 | 19 | @implementation RHAppDelegate 20 | 21 | @synthesize window = _window; 22 | @synthesize navigationController = _navigationController; 23 | 24 | -(void)dealloc 25 | { 26 | [_window release]; 27 | [_navigationController release]; 28 | [super dealloc]; 29 | } 30 | 31 | -(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 32 | { 33 | self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease]; 34 | // Override point for customization after application launch. 35 | 36 | //perf setup. 5000 contacts 37 | #if PERF_TEST_SETUP 38 | RHAddressBook *sab = [[[RHAddressBook alloc] init] autorelease]; 39 | int count = 5000 - [sab numberOfPeople]; 40 | while (count > 0) { 41 | RHPerson *new = [sab newPersonInDefaultSource]; 42 | new.firstName = [NSString stringWithFormat:@"T1 %i", count]; 43 | new.lastName = [NSString stringWithFormat:@"T2 %i", count]; 44 | count--; 45 | } 46 | [sab save]; 47 | NSLog(@"setup complete"); 48 | exit(EXIT_SUCCESS); 49 | #endif 50 | 51 | #if PERF_TEST 52 | clock_t start_time = 0; 53 | clock_t end_time = 0; 54 | 55 | start_time = clock(); 56 | RHAddressBook *pab = [[[RHAddressBook alloc] init] autorelease]; 57 | end_time = clock(); 58 | 59 | NSLog(@"PERF: Init took %f seconds.", (double)(end_time - start_time) / (double)CLOCKS_PER_SEC); 60 | 61 | start_time = clock(); 62 | NSArray *people = [pab people]; 63 | end_time = clock(); 64 | 65 | NSLog(@"PERF: First people call took %f seconds. (for %lu people)", (double)(end_time - start_time) / (double)CLOCKS_PER_SEC, (unsigned long)[people count]); 66 | 67 | start_time = clock(); 68 | NSArray *people2 = [pab people]; 69 | end_time = clock(); 70 | 71 | NSLog(@"PERF: Second people call took %f seconds. (for %lu people)", (double)(end_time - start_time) / (double)CLOCKS_PER_SEC, (unsigned long)[people2 count]); 72 | 73 | start_time = clock(); 74 | [pab save]; 75 | end_time = clock(); 76 | 77 | NSLog(@"PERF: Save call took %f seconds. (for %lu people)", (double)(end_time - start_time) / (double)CLOCKS_PER_SEC, (unsigned long)[people2 count]); 78 | 79 | #endif 80 | 81 | RHAddressBook *ab = [[[RHAddressBook alloc] init] autorelease]; 82 | RHAddressBookViewController *abViewController = [[[RHAddressBookViewController alloc] initWithAddressBook:ab] autorelease]; 83 | self.navigationController = [[[UINavigationController alloc] initWithRootViewController:abViewController] autorelease]; 84 | self.window.rootViewController = self.navigationController; 85 | [self.window makeKeyAndVisible]; 86 | 87 | 88 | 89 | 90 | 91 | //if not yet authorized, force an auth. 92 | if ([RHAddressBook authorizationStatus] == RHAuthorizationStatusNotDetermined){ 93 | [ab requestAuthorizationWithCompletion:^(bool granted, NSError *error) { 94 | [abViewController setAddressBook:ab]; 95 | }]; 96 | } 97 | 98 | // warn re being denied access to contacts 99 | if ([RHAddressBook authorizationStatus] == RHAuthorizationStatusDenied){ 100 | UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"RHAuthorizationStatusDenied" message:@"Access to the addressbook is currently denied." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; 101 | [alert show]; 102 | [alert release]; 103 | } 104 | 105 | // warn re restricted access to contacts 106 | if ([RHAddressBook authorizationStatus] == RHAuthorizationStatusRestricted){ 107 | UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"RHAuthorizationStatusRestricted" message:@"Access to the addressbook is currently restricted." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; 108 | [alert show]; 109 | [alert release]; 110 | } 111 | 112 | return YES; 113 | } 114 | 115 | -(void)applicationWillResignActive:(UIApplication *)application 116 | { 117 | /* 118 | 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. 119 | 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. 120 | */ 121 | } 122 | 123 | -(void)applicationDidEnterBackground:(UIApplication *)application 124 | { 125 | /* 126 | 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. 127 | If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 128 | */ 129 | } 130 | 131 | -(void)applicationWillEnterForeground:(UIApplication *)application 132 | { 133 | /* 134 | 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. 135 | */ 136 | } 137 | 138 | -(void)applicationDidBecomeActive:(UIApplication *)application 139 | { 140 | /* 141 | 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. 142 | */ 143 | } 144 | 145 | -(void)applicationWillTerminate:(UIApplication *)application 146 | { 147 | /* 148 | Called when the application is about to terminate. 149 | Save data if appropriate. 150 | See also applicationDidEnterBackground:. 151 | */ 152 | } 153 | 154 | @end 155 | -------------------------------------------------------------------------------- /RHAddressBook/RHMultiValue.m: -------------------------------------------------------------------------------- 1 | // 2 | // RHMultiValue.m 3 | // RHAddressBook 4 | // 5 | // Created by Richard Heard on 11/11/11. 6 | // Copyright (c) 2011 Richard Heard. All rights reserved. 7 | // 8 | // Redistribution and use in source and binary forms, with or without 9 | // modification, are permitted provided that the following conditions 10 | // are met: 11 | // 1. Redistributions of source code must retain the above copyright 12 | // notice, this list of conditions and the following disclaimer. 13 | // 2. Redistributions in binary form must reproduce the above copyright 14 | // notice, this list of conditions and the following disclaimer in the 15 | // documentation and/or other materials provided with the distribution. 16 | // 3. The name of the author may not be used to endorse or promote products 17 | // derived from this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | // 30 | 31 | #import "RHMultiValue.h" 32 | 33 | #import "RHPerson.h" 34 | #import "RHRecord.h" 35 | 36 | @implementation RHMultiValue 37 | 38 | @synthesize multiValueRef=_multiValueRef; 39 | 40 | #pragma mark - init 41 | -(instancetype)initWithMultiValueRef:(ABMultiValueRef)multiValueRef{ 42 | self = [super init]; 43 | if (self){ 44 | if (!multiValueRef){ 45 | arc_release_nil(self); 46 | return nil; 47 | } 48 | 49 | _multiValueRef = CFRetain(multiValueRef); 50 | } 51 | return self; 52 | } 53 | 54 | -(void)dealloc{ 55 | if (_multiValueRef) CFRelease(_multiValueRef); 56 | _multiValueRef = NULL; 57 | 58 | arc_super_dealloc(); 59 | } 60 | 61 | #pragma mark - basic accessors 62 | 63 | -(ABPropertyType)propertyType{ 64 | return ABMultiValueGetPropertyType(_multiValueRef); 65 | } 66 | 67 | - (NSUInteger)count{ 68 | return ABMultiValueGetCount(_multiValueRef); 69 | } 70 | 71 | //values 72 | -(id)valueAtIndex:(NSUInteger)index{ 73 | id value = (id)ARCBridgingRelease(ABMultiValueCopyValueAtIndex(_multiValueRef, index)); 74 | return value; 75 | } 76 | 77 | -(NSArray*)values{ 78 | NSArray* values = (NSArray*)ARCBridgingRelease(ABMultiValueCopyArrayOfAllValues(_multiValueRef)); 79 | return values; 80 | } 81 | 82 | //labels 83 | -(NSString*)labelAtIndex:(NSUInteger)index{ 84 | NSString* label = (NSString*)ARCBridgingRelease(ABMultiValueCopyLabelAtIndex(_multiValueRef, index)); 85 | return label; 86 | } 87 | 88 | -(NSString*)localizedLabelAtIndex:(NSUInteger)index{ 89 | return [RHPerson localizedLabel:[self labelAtIndex:index]]; 90 | } 91 | 92 | //identifier 93 | -(NSUInteger)indexForIdentifier:(ABMultiValueIdentifier)identifier{ 94 | return ABMultiValueGetIndexForIdentifier(_multiValueRef, identifier); 95 | } 96 | 97 | -(ABMultiValueIdentifier)identifierAtIndex:(NSUInteger)index{ 98 | return ABMultiValueGetIdentifierAtIndex(_multiValueRef, index); 99 | } 100 | 101 | //convenience accessor 102 | -(NSUInteger)firstIndexOfValue:(id)value{ 103 | return ABMultiValueGetFirstIndexOfValue(_multiValueRef, (__bridge CFTypeRef)(value)); 104 | } 105 | 106 | //mutable copy 107 | -(RHMutableMultiValue*)mutableCopy{ 108 | //first make a mutable ref 109 | ABMutableMultiValueRef mutableRef = ABMultiValueCreateMutableCopy(_multiValueRef); 110 | 111 | //then create a mutable wrapper instance 112 | RHMutableMultiValue *new = nil; 113 | if (mutableRef){ 114 | new = [[RHMutableMultiValue alloc] initWithMultiValueRef:mutableRef]; 115 | CFRelease(mutableRef); 116 | } 117 | 118 | return new; 119 | } 120 | 121 | 122 | #pragma mark - misc 123 | 124 | -(NSString*)contentDescription{ 125 | NSString *result = @""; 126 | 127 | NSUInteger index = [self count]; 128 | while (index > 0) { 129 | index--; 130 | result = [NSString stringWithFormat:@"\t%lu) %@=%@\n%@", (unsigned long)index, [self labelAtIndex:index], [self valueAtIndex:index], result]; 131 | } 132 | 133 | return result; 134 | } 135 | 136 | -(NSString*)description{ 137 | return [NSString stringWithFormat:@"%@: <%p> type:%@ count:%lu contents:{%@}", NSStringFromClass([self class]), self, [RHRecord descriptionForPropertyType:[self propertyType]], (unsigned long)[self count], [self contentDescription]]; 138 | } 139 | 140 | -(BOOL)isEqual:(id)object{ 141 | if (self == object) return YES; 142 | if (!object) return NO; 143 | if (![object isKindOfClass:[RHMultiValue class]]) return NO; 144 | 145 | return [self isEqualToMultiValue:object]; 146 | } 147 | -(BOOL)isEqualToMultiValue:(RHMultiValue*)otherMultiValue{ 148 | 149 | if (![self count] == [otherMultiValue count]) return NO; 150 | 151 | for (int i = 0; i < [self count]; i++) { 152 | if (! [[self labelAtIndex:i] isEqualToString:[otherMultiValue labelAtIndex:i]]) return NO; 153 | if (! [[self valueAtIndex:i] isEqual:[otherMultiValue valueAtIndex:i]]) return NO; 154 | } 155 | return YES; 156 | } 157 | 158 | @end 159 | 160 | 161 | @implementation RHMutableMultiValue 162 | 163 | #pragma mark - basic modifiers (mutable) 164 | 165 | -(instancetype)initWithType:(ABPropertyType)newPropertyType{ 166 | ABMultiValueRef multiValueRef = ABMultiValueCreateMutable(newPropertyType); 167 | id new = nil; 168 | if (multiValueRef){ 169 | new = [self initWithMultiValueRef:multiValueRef]; 170 | CFRelease(multiValueRef); 171 | } 172 | return new; 173 | } 174 | 175 | 176 | -(ABMultiValueIdentifier)addValue:(id)value withLabel:(NSString *)label{ 177 | ABMultiValueIdentifier idOut = kABMultiValueInvalidIdentifier; 178 | ABMultiValueAddValueAndLabel(_multiValueRef, (__bridge CFTypeRef)(value), (__bridge CFStringRef)label, &idOut); 179 | return idOut; 180 | } 181 | 182 | -(ABMultiValueIdentifier)insertValue:(id)value withLabel:(NSString *)label atIndex:(NSUInteger)index{ 183 | ABMultiValueIdentifier idOut = kABMultiValueInvalidIdentifier; 184 | ABMultiValueInsertValueAndLabelAtIndex(_multiValueRef, (__bridge CFTypeRef)(value), (__bridge CFStringRef)label, index, &idOut); 185 | return idOut; 186 | } 187 | 188 | -(BOOL)removeValueAndLabelAtIndex:(NSUInteger)index{ 189 | return ABMultiValueRemoveValueAndLabelAtIndex(_multiValueRef, index); 190 | } 191 | 192 | -(BOOL)replaceValueAtIndex:(NSUInteger)index withValue:(id)value{ 193 | return ABMultiValueReplaceValueAtIndex(_multiValueRef, (__bridge CFTypeRef)(value), index); 194 | } 195 | 196 | -(BOOL)replaceLabelAtIndex:(NSUInteger)index withLabel:(NSString*)label{ 197 | return ABMultiValueReplaceLabelAtIndex(_multiValueRef, (__bridge CFStringRef)label, index); 198 | } 199 | 200 | @end 201 | -------------------------------------------------------------------------------- /RHAddressBook/RHPersonLabels.h: -------------------------------------------------------------------------------- 1 | // 2 | // RHPersonLabels.h 3 | // RHAddressBook 4 | // 5 | // Created by Richard Heard on 22/03/12. 6 | // Copyright (c) 2012 Richard Heard. All rights reserved. 7 | // 8 | // Redistribution and use in source and binary forms, with or without 9 | // modification, are permitted provided that the following conditions 10 | // are met: 11 | // 1. Redistributions of source code must retain the above copyright 12 | // notice, this list of conditions and the following disclaimer. 13 | // 2. Redistributions in binary form must reproduce the above copyright 14 | // notice, this list of conditions and the following disclaimer in the 15 | // documentation and/or other materials provided with the distribution. 16 | // 3. The name of the author may not be used to endorse or promote products 17 | // derived from this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | // 30 | 31 | #ifndef RHAddressBook_RHPersonLabels_h 32 | #define RHAddressBook_RHPersonLabels_h 33 | 34 | 35 | // Generic labels 36 | #define RHWorkLabel (NSString*)kABWorkLabel 37 | #define RHHomeLabel (NSString*)kABHomeLabel 38 | #define RHOtherLabel (NSString*)kABOtherLabel 39 | 40 | 41 | // Addresses 42 | #define RHPersonAddressStreetKey (NSString*)kABPersonAddressStreetKey 43 | #define RHPersonAddressCityKey (NSString*)kABPersonAddressCityKey 44 | #define RHPersonAddressStateKey (NSString*)kABPersonAddressStateKey 45 | #define RHPersonAddressZIPKey (NSString*)kABPersonAddressZIPKey 46 | #define RHPersonAddressCountryKey (NSString*)kABPersonAddressCountryKey 47 | #define RHPersonAddressCountryCodeKey (NSString*)kABPersonAddressCountryCodeKey 48 | 49 | 50 | // Dates 51 | #define RHPersonAnniversaryLabel (NSString*)kABPersonAnniversaryLabel 52 | 53 | // Kind 54 | #define RHPersonKindPerson (NSNumber*)kABPersonKindPerson 55 | #define RHPersonKindOrganization (NSNumber*)kABPersonKindOrganization 56 | 57 | 58 | // Phone numbers 59 | #define RHPersonPhoneMobileLabel (NSString*)kABPersonPhoneMobileLabel 60 | #define RHPersonPhoneIPhoneLabel (NSString*)kABPersonPhoneIPhoneLabel //3.0 61 | #define RHPersonPhoneMainLabel (NSString*)kABPersonPhoneMainLabel 62 | #define RHPersonPhoneHomeFAXLabel (NSString*)kABPersonPhoneHomeFAXLabel 63 | #define RHPersonPhoneWorkFAXLabel (NSString*)kABPersonPhoneWorkFAXLabel 64 | #define RHPersonPhoneOtherFAXLabel (NSString*)kABPersonPhoneOtherFAXLabel //5.0 65 | #define RHPersonPhonePagerLabel (NSString*)kABPersonPhonePagerLabel 66 | 67 | 68 | // IM 69 | #define RHPersonInstantMessageServiceKey (NSString*)kABPersonInstantMessageServiceKey // Service ("Yahoo", "Jabber", etc.) 70 | #define RHPersonInstantMessageServiceYahoo (NSString*)kABPersonInstantMessageServiceYahoo 71 | #define RHPersonInstantMessageServiceJabber (NSString*)kABPersonInstantMessageServiceJabber 72 | #define RHPersonInstantMessageServiceMSN (NSString*)kABPersonInstantMessageServiceMSN 73 | #define RHPersonInstantMessageServiceICQ (NSString*)kABPersonInstantMessageServiceICQ 74 | #define RHPersonInstantMessageServiceAIM (NSString*)kABPersonInstantMessageServiceAIM 75 | #define RHPersonInstantMessageServiceQQ (NSString*)kABPersonInstantMessageServiceQQ //5.0 76 | #define RHPersonInstantMessageServiceGoogleTalk (NSString*)kABPersonInstantMessageServiceGoogleTalk //5.0 77 | #define RHPersonInstantMessageServiceSkype (NSString*)kABPersonInstantMessageServiceSkype //5.0 78 | #define RHPersonInstantMessageServiceFaceboo (NSString*)kABPersonInstantMessageServiceFaceboo //5.0 79 | #define RHPersonInstantMessageServiceGaduGadu (NSString*)kABPersonInstantMessageServiceGaduGadu //5.0 80 | 81 | #define RHPersonInstantMessageUsernameKey (NSString*)kABPersonInstantMessageUsernameKey // Username 82 | 83 | 84 | // URLs 85 | #define RHPersonHomePageLabel (NSString*)kABPersonHomePageLabel // Home Page 86 | 87 | 88 | // Related names 89 | #define RHPersonFatherLabel (NSString*)kABPersonFatherLabel // Father 90 | #define RHPersonMotherLabel (NSString*)kABPersonMotherLabel // Mother 91 | #define RHPersonParentLabel (NSString*)kABPersonParentLabel // Parent 92 | #define RHPersonBrotherLabel (NSString*)kABPersonBrotherLabel // Brother 93 | #define RHPersonSisterLabel (NSString*)kABPersonSisterLabel // Sister 94 | #define RHPersonChildLabel (NSString*)kABPersonChildLabel // Child 95 | #define RHPersonFriendLabel (NSString*)kABPersonFriendLabel // Friend 96 | #define RHPersonSpouseLabel (NSString*)kABPersonSpouseLabel // Spouse 97 | #define RHPersonPartnerLabel (NSString*)kABPersonPartnerLabel // Partner 98 | #define RHPersonAssistantLabel (NSString*)kABPersonAssistantLabel // Assistant 99 | #define RHPersonManagerLabel (NSString*)kABPersonManagerLabel // Manager 100 | 101 | 102 | // Social Profile 103 | #define RHPersonSocialProfileURLKey (NSString*)kABPersonSocialProfileURLKey //5.0 string representation of a url for the social profile 104 | //5.0 the following properties are optional 105 | #define RHPersonSocialProfileServiceKey (NSString*)kABPersonSocialProfileServiceKey //5.0 string representing the name of the service (Twitter, Facebook, LinkedIn, etc.) 106 | #define RHPersonSocialProfileUsernameKey (NSString*)kABPersonSocialProfileUsernameKey //5.0 string representing the user visible name 107 | #define RHPersonSocialProfileUserIdentifierKey (NSString*)kABPersonSocialProfileUserIdentifierKey //5.0 string representing the service specific identifier (optional) 108 | 109 | #define RHPersonSocialProfileServiceTwitter (NSString*)kABPersonSocialProfileServiceTwitter //5.0 110 | #define RHPersonSocialProfileServiceGameCenter (NSString*)kABPersonSocialProfileServiceGameCenter //5.0 111 | #define RHPersonSocialProfileServiceFacebook (NSString*)kABPersonSocialProfileServiceFacebook //5.0 112 | #define RHPersonSocialProfileServiceMyspace (NSString*)kABPersonSocialProfileServiceMyspace //5.0 113 | #define RHPersonSocialProfileServiceLinkedIn (NSString*)kABPersonSocialProfileServiceLinkedIn //5.0 114 | #define RHPersonSocialProfileServiceFlickr (NSString*)kABPersonSocialProfileServiceFlickr //5.0 115 | 116 | 117 | #endif 118 | -------------------------------------------------------------------------------- /RHAddressBook/RHGroup.m: -------------------------------------------------------------------------------- 1 | // 2 | // RHGroup.m 3 | // RHAddressBook 4 | // 5 | // Created by Richard Heard on 14/11/11. 6 | // Copyright (c) 2011 Richard Heard. All rights reserved. 7 | // 8 | // Redistribution and use in source and binary forms, with or without 9 | // modification, are permitted provided that the following conditions 10 | // are met: 11 | // 1. Redistributions of source code must retain the above copyright 12 | // notice, this list of conditions and the following disclaimer. 13 | // 2. Redistributions in binary form must reproduce the above copyright 14 | // notice, this list of conditions and the following disclaimer in the 15 | // documentation and/or other materials provided with the distribution. 16 | // 3. The name of the author may not be used to endorse or promote products 17 | // derived from this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | // 30 | 31 | #import "RHGroup.h" 32 | #import "RHRecord_Private.h" 33 | 34 | #import "RHAddressBook.h" 35 | #import "RHPerson.h" 36 | #import "RHSource.h" 37 | 38 | 39 | @implementation RHGroup 40 | 41 | #pragma mark - group creator methods 42 | +(instancetype)newGroupInSource:(RHSource*)source{ 43 | ABRecordRef newGroupRef = ABGroupCreateInSource(source.recordRef); 44 | RHGroup *newGroup = nil; 45 | if (newGroupRef){ 46 | newGroup = [[RHGroup alloc] initWithAddressBook:source.addressBook recordRef:newGroupRef]; 47 | CFRelease(newGroupRef); 48 | } 49 | 50 | return newGroup; 51 | } 52 | 53 | #pragma mark - properties 54 | -(NSString*)name{ 55 | return [self getBasicValueForPropertyID:kABGroupNameProperty]; 56 | } 57 | 58 | -(void)setName:(NSString *)name{ 59 | NSError *error = nil; 60 | if (![self setBasicValue:(CFStringRef)name forPropertyID:kABGroupNameProperty error:&error]){ 61 | RHErrorLog(@"-[RHGroup %@] error:%@", NSStringFromSelector(_cmd), error); 62 | } 63 | } 64 | 65 | -(RHSource*)source{ 66 | RHSource *result = nil; 67 | ABRecordRef source = ABGroupCopySource(_recordRef); 68 | if (source){ 69 | result = [_addressBook sourceForABRecordRef:source]; 70 | CFRelease(source); 71 | } 72 | return result; 73 | } 74 | 75 | -(NSInteger)count{ 76 | return [[self members] count]; 77 | } 78 | 79 | #pragma mark - add / remove 80 | -(BOOL)addMember:(RHPerson*)person{ 81 | if (person.addressBook != self.addressBook) return NO; 82 | 83 | __block BOOL success = NO; 84 | [self performRecordAction:^(ABRecordRef recordRef) { 85 | CFErrorRef errorRef = NULL; 86 | success = ABGroupAddMember(recordRef, person.recordRef, &errorRef); 87 | if (!success) { 88 | RHErrorLog(@"RHGroup: Error adding member. %@", errorRef); 89 | if (errorRef) CFRelease(errorRef); 90 | } 91 | } waitUntilDone:YES]; 92 | return success; 93 | } 94 | 95 | -(BOOL)removeMember:(RHPerson*)person{ 96 | __block BOOL success = NO; 97 | [self performRecordAction:^(ABRecordRef recordRef) { 98 | CFErrorRef errorRef = NULL; 99 | success = ABGroupRemoveMember(recordRef, person.recordRef, &errorRef); 100 | if (!success) { 101 | RHErrorLog(@"RHGroup: Error removing member. %@", errorRef); 102 | if (errorRef) CFRelease(errorRef); 103 | } 104 | } waitUntilDone:YES]; 105 | return success; 106 | } 107 | 108 | -(void)removeAllMembers{ 109 | NSArray *members = [self members]; 110 | for (RHPerson *person in members) { 111 | [self removeMember:person]; 112 | } 113 | } 114 | 115 | 116 | #pragma mark - access 117 | -(NSArray*)members{ 118 | NSMutableArray *members = [NSMutableArray array]; 119 | __block CFArrayRef memberRefs = NULL; 120 | 121 | [self performRecordAction:^(ABRecordRef recordRef) { 122 | memberRefs = ABGroupCopyArrayOfAllMembers(recordRef); 123 | } waitUntilDone:YES]; 124 | 125 | 126 | if (memberRefs){ 127 | for (int i = 0; i < CFArrayGetCount(memberRefs); i++) { 128 | ABRecordRef memberRef = CFArrayGetValueAtIndex(memberRefs, i); 129 | 130 | RHPerson *person = [_addressBook personForABRecordRef:memberRef]; 131 | if (person) { 132 | [members addObject:person]; 133 | } else { 134 | RHLog(@"Failed to find member"); 135 | } 136 | } 137 | 138 | CFRelease(memberRefs); 139 | } 140 | 141 | return [NSArray arrayWithArray:members]; 142 | 143 | } 144 | 145 | -(NSArray*)membersOrderedBySortOrdering:(ABPersonSortOrdering)ordering{ 146 | NSMutableArray *members = [NSMutableArray array]; 147 | __block CFArrayRef memberRefs = nil; 148 | 149 | [self performRecordAction:^(ABRecordRef recordRef) { 150 | memberRefs = ABGroupCopyArrayOfAllMembersWithSortOrdering(recordRef, ordering); 151 | } waitUntilDone:YES]; 152 | 153 | if (memberRefs){ 154 | for (int i = 0; i < CFArrayGetCount(memberRefs); i++) { 155 | ABRecordRef memberRef = CFArrayGetValueAtIndex(memberRefs, i); 156 | 157 | RHPerson *person = [_addressBook personForABRecordRef:memberRef]; 158 | if (person){ 159 | [members addObject:person]; 160 | } else { 161 | RHLog(@"Failed to find member"); 162 | } 163 | } 164 | 165 | CFRelease(memberRefs); 166 | } 167 | 168 | return [NSArray arrayWithArray:members]; 169 | } 170 | 171 | -(NSArray*)membersOrderedByFirstName{ 172 | return [self membersOrderedBySortOrdering:kABPersonSortByFirstName]; 173 | } 174 | 175 | -(NSArray*)membersOrderedByLastName{ 176 | return [self membersOrderedBySortOrdering:kABPersonSortByLastName]; 177 | } 178 | 179 | -(NSArray*)membersOrderedByUsersPreference{ 180 | return [self membersOrderedBySortOrdering:[RHAddressBook sortOrdering]]; 181 | } 182 | 183 | 184 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 185 | -(NSData*)vCardRepresentationForMembers{ 186 | return [_addressBook vCardRepresentationForPeople:[self members]]; 187 | } 188 | 189 | #if RH_AB_INCLUDE_GEOCODING 190 | -(NSArray*)membersWithinDistance:(double)distance ofLocation:(CLLocation*)location{ 191 | 192 | NSArray *allWithinDistance = [_addressBook peopleWithinDistance:distance ofLocation:location]; 193 | NSArray *allMembers = [self members]; 194 | 195 | NSIndexSet *inRangeMemberIndexes = [allWithinDistance indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) { 196 | return [allMembers containsObject:obj]; 197 | }]; 198 | 199 | return [allWithinDistance objectsAtIndexes:inRangeMemberIndexes]; 200 | } 201 | #endif // Geocoding 202 | 203 | #endif //end iOS5+ 204 | 205 | 206 | #pragma mark - remove 207 | -(BOOL)remove{ 208 | return [_addressBook removeGroup:self]; 209 | } 210 | 211 | 212 | #pragma mark - misc 213 | -(NSString*)description{ 214 | return [NSString stringWithFormat:@"<%@: %p> name:%@", NSStringFromClass([self class]), self, self.name]; 215 | } 216 | 217 | @end 218 | -------------------------------------------------------------------------------- /RHAddressBookTester/RHGroupViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // RHGroupViewController.m 3 | // RHAddressBook 4 | // 5 | // Created by Richard Heard on 21/02/12. 6 | // Copyright (c) 2012 Richard Heard. All rights reserved. 7 | // 8 | 9 | #import "RHGroupViewController.h" 10 | 11 | #import 12 | 13 | @interface RHGroupViewController () 14 | 15 | -(void)addNewPerson; 16 | 17 | -(void)addressBookChanged:(NSNotification*)notification; 18 | 19 | @end 20 | 21 | @implementation RHGroupViewController 22 | 23 | @synthesize group=_group; 24 | 25 | - (instancetype)initWithGroup:(RHGroup*)group{ 26 | self = [super initWithStyle:UITableViewStylePlain]; 27 | if (self) { 28 | _group = [group retain]; 29 | 30 | //set out title 31 | self.title = [_group compositeName]; 32 | } 33 | return self; 34 | } 35 | 36 | #define RN(x) [x release]; x = nil; 37 | - (void)dealloc { 38 | RN(_members); 39 | RN(_group); 40 | 41 | [[NSNotificationCenter defaultCenter] removeObserver:self]; //for the ab externally changed notifications 42 | 43 | [super dealloc]; 44 | } 45 | - (void)viewDidLoad { 46 | [super viewDidLoad]; 47 | 48 | self.navigationItem.rightBarButtonItem = self.editButtonItem; 49 | 50 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(addressBookChanged:) name:RHAddressBookExternalChangeNotification object:nil]; 51 | 52 | self.tableView.allowsSelectionDuringEditing = YES; 53 | 54 | } 55 | 56 | - (void)viewDidUnload { 57 | [super viewDidUnload]; 58 | // Release any retained subviews of the main view. 59 | 60 | //discard our cached values 61 | RN(_members); 62 | 63 | [[NSNotificationCenter defaultCenter] removeObserver:self name:RHAddressBookExternalChangeNotification object:nil]; 64 | 65 | } 66 | 67 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { 68 | return (interfaceOrientation == UIInterfaceOrientationPortrait); 69 | } 70 | 71 | 72 | - (void)setEditing:(BOOL)editing animated:(BOOL)animated { 73 | [super setEditing:editing animated:animated]; 74 | [self.tableView setEditing:editing animated:animated]; 75 | 76 | if(editing) { 77 | [self.tableView insertSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationFade]; 78 | self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:@"Rename" style:UIBarButtonItemStylePlain target:self action:@selector(renameGroup)] autorelease]; 79 | 80 | } else { 81 | [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationFade]; 82 | self.navigationItem.leftBarButtonItem = self.navigationItem.backBarButtonItem; 83 | 84 | } 85 | } 86 | 87 | 88 | #pragma mark - Table view data source 89 | 90 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 91 | // Return the number of sections. 92 | 93 | return self.editing ? 2 : 1; 94 | } 95 | 96 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 97 | if (section == 0){ 98 | // Return the number of rows in the section. 99 | [_members release]; 100 | _members = [[_group membersOrderedByUsersPreference] mutableCopy]; 101 | 102 | // this is as good a place as any to update our title. 103 | self.title = [_group compositeName]; 104 | 105 | return [_members count]; 106 | } else if (section == 1 && self.editing){ 107 | return 1; 108 | } else { 109 | return 0; 110 | } 111 | } 112 | 113 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 114 | static NSString *cellIdentifier = @"Cell"; 115 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; 116 | 117 | if (!cell) cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease]; 118 | 119 | if (indexPath.section == 0){ 120 | RHPerson *person = [_members objectAtIndex:indexPath.row]; 121 | cell.textLabel.text = person.compositeName; 122 | cell.imageView.image = person.thumbnail; 123 | cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; 124 | } else { 125 | //assume adding a new row 126 | cell.textLabel.text = NSLocalizedString(@"Add New Person...", nil); 127 | cell.accessoryType = UITableViewCellAccessoryNone; 128 | } 129 | return cell; 130 | } 131 | 132 | 133 | // Override to support conditional editing of the table view. 134 | - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath 135 | { 136 | return YES; 137 | } 138 | 139 | - (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {; 140 | if (indexPath.section == 1) return UITableViewCellEditingStyleInsert; 141 | 142 | return UITableViewCellEditingStyleDelete; 143 | } 144 | 145 | 146 | // Override to support editing the table view. 147 | - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath 148 | { 149 | if (editingStyle == UITableViewCellEditingStyleDelete) { 150 | // Delete the row from the data source 151 | RHPerson *member = [_members objectAtIndex:indexPath.row]; 152 | [_members removeObject:member]; 153 | [_group removeMember:member]; 154 | [_group save]; 155 | [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; 156 | } else if (editingStyle == UITableViewCellEditingStyleInsert) { 157 | // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view 158 | [self addNewPerson]; 159 | } 160 | 161 | } 162 | 163 | /* 164 | // Override to support rearranging the table view. 165 | - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath 166 | { 167 | } 168 | */ 169 | 170 | /* 171 | // Override to support conditional rearranging of the table view. 172 | - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath 173 | { 174 | // Return NO if you do not want the item to be re-orderable. 175 | return YES; 176 | } 177 | */ 178 | 179 | #pragma mark - Table view delegate 180 | 181 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 182 | if (indexPath.section == 0){ 183 | //TODO: push our own viewer view, for now just use the AB default one. 184 | RHPerson *person = [_members objectAtIndex:indexPath.row]; 185 | 186 | ABPersonViewController *personViewController = [[[ABPersonViewController alloc] init] autorelease]; 187 | 188 | //setup (tell the view controller to use our underlying address book instance, so our person object is directly updated) 189 | [person.addressBook performAddressBookAction:^(ABAddressBookRef addressBookRef) { 190 | personViewController.addressBook =addressBookRef; 191 | } waitUntilDone:YES]; 192 | 193 | personViewController.displayedPerson = person.recordRef; 194 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 195 | personViewController.allowsActions = YES; 196 | #endif 197 | personViewController.allowsEditing = YES; 198 | 199 | 200 | [self.navigationController pushViewController:personViewController animated:YES]; 201 | } else { 202 | //assume adding a person. 203 | [self addNewPerson]; 204 | } 205 | } 206 | 207 | #pragma mark - add 208 | -(void)addNewPerson{ 209 | RHPerson *person = [_group.addressBook newPersonInSource:_group.source]; 210 | person.firstName = NSLocalizedString(@"New Person", nil); 211 | [_group addMember:person]; 212 | [_group save]; 213 | [_members addObject:person]; 214 | [person release]; 215 | } 216 | 217 | #pragma mark - edit 218 | 219 | -(void)renameGroup{ 220 | UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Rename group?" message:@"" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"OK", nil]; 221 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 222 | if ([UIAlertView instancesRespondToSelector:@selector(setAlertViewStyle:)]){ 223 | [alert setAlertViewStyle:UIAlertViewStylePlainTextInput]; 224 | [[alert textFieldAtIndex:0] setText:[_group name]]; 225 | } 226 | #endif 227 | [alert show]; 228 | [alert release]; 229 | } 230 | 231 | - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{ 232 | if (buttonIndex == 1){ 233 | if ([UIAlertView instancesRespondToSelector:@selector(setAlertViewStyle:)]){ 234 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 235 | _group.name = [[alertView textFieldAtIndex:0] text]; 236 | [_group save]; 237 | #endif 238 | } else { 239 | _group.name = [NSString stringWithFormat:@"%@ R", _group.name]; 240 | [_group save]; 241 | } 242 | } 243 | } 244 | 245 | #pragma mark - addressBookChangedNotification 246 | -(void)addressBookChanged:(NSNotification*)notification{ 247 | [self.tableView reloadData]; 248 | } 249 | 250 | 251 | @end 252 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## RHAddressBook 2 | A Cocoa / Objective-C library for interfacing with the iOS AddressBook with added geocoding support. 3 | 4 | * All attributes on various objects are exposed as properties, allowing for simple Obj-C code. (No more dealing with CF methods etc ) 5 | * Built in support for background Geocoding with an in-built persistent cache. (iOS5+ only) 6 | * vCard import and export for single and multiple people. 7 | * Access to all underlying ABRecordRefs & ABAddressBookRefs etc. 8 | * Maintains an underlying thread for each ab instance in-order to ensure thread safety. 9 | * Sends NSNotifications when ab has changed. 10 | * Geocoding is disabled by default. (See RH_AB_INCLUDE_GEOCODING) 11 | 12 | 13 | ### Bonus Features 14 | * Unit Tests. 15 | * Basic Demo App. 16 | 17 | ## Classes 18 | * RHAddressBook 19 | * RHSource - Representation of various address-book sources found on the iPhone 20 | * RHGroup 21 | * RHPerson - Represents a person in the addressbook. 22 | * RHMultiValue - Represents multiple key/value pairs. Used for RHPersons addresses etc. 23 | 24 | ## Getting Started 25 | Include RHAddressBook in your iOS project. 26 | 27 | ```objectivec 28 | #import 29 | ``` 30 | Getting an instance of the addressbook. 31 | 32 | ```objectivec 33 | RHAddressBook *ab = [[[RHAddressBook alloc] init] autorelease]; 34 | ``` 35 | Support for iOS6+ authorization 36 | 37 | ```objectivec 38 | //query current status, pre iOS6 always returns Authorized 39 | if ([RHAddressBook authorizationStatus] == RHAuthorizationStatusNotDetermined){ 40 | 41 | //request authorization 42 | [ab requestAuthorizationWithCompletion:^(bool granted, NSError *error) { 43 | [abViewController setAddressBook:ab]; 44 | }]; 45 | } 46 | ``` 47 | Registering for addressbook changes 48 | 49 | ```objectivec 50 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(addressBookChanged:) name:RHAddressBookExternalChangeNotification object:nil]; 51 | ``` 52 | Getting sources. 53 | 54 | ```objectivec 55 | NSArray *sources = [ab sources]; 56 | RHSource *defaultSource = [ab defaultSource]; 57 | ``` 58 | Getting a list of groups. 59 | 60 | ```objectivec 61 | NSArray *groups = [ab groups]; 62 | long numberOfGroups = [ab numberOfGroups]; 63 | NSArray *groupsInSource = [ab groupsInSource:defaultSource]; 64 | RHGroup *lastGroup = [groups lastObject]; 65 | ``` 66 | Getting a list of people. 67 | 68 | ```objectivec 69 | NSArray *allPeople = [ab people]; 70 | long numberOfPeople = [ab numberOfPeople]; 71 | NSArray *allPeopleSorted = [ab peopleOrderedByUsersPreference]; 72 | NSArray *allFreds = [ab peopleWithName:@"Fred"]; 73 | NSArray *allFredsInLastGroup = [lastGroup peopleWithName:@"Fred"]; 74 | RHPerson *person = [allPeople lastObject]; 75 | ``` 76 | Getting basic properties on on a person. 77 | 78 | ```objectivec 79 | NSString *department = [person department]; 80 | UIImage *thumbnail = [person thumbnail]; 81 | BOOL isCompany = [person isOrganization]; 82 | ``` 83 | Setting basic properties on a person. 84 | 85 | ```objectivec 86 | person.name = @"Freddie"; 87 | [person setImage:[UIImage imageNames:@"hahaha.jpg"]]; 88 | person.kind = kABPersonKindOrganization; 89 | [person save]; 90 | ``` 91 | Getting MultiValue properties on a person. 92 | 93 | ```objectivec 94 | RHMultiDictionaryValue *addressesMultiValue = [person addresses]; 95 | NSString *firstAddressLabel = [RHPerson localizedLabel:[addressesMultiValue labelAtIndex]]; //eg Home 96 | NSDictionary *firstAddress = [addressesMultiValue valueAtIndex:0]; 97 | ``` 98 | Setting MultiValue properties on a person. 99 | 100 | ```objectivec 101 | RHMultiStringValue *phoneMultiValue = [person phoneNumbers]; 102 | RHMutableMultiStringValue *mutablePhoneMultiValue = [[phoneMultiValue mutableCopy] autorelease]; 103 | if (! mutablePhoneMultiValue) mutablePhoneMultiValue = [[[RHMutableMultiStringValue alloc] initWithType:kABMultiStringPropertyType] autorelease]; 104 | 105 | //RHPersonPhoneIPhoneLabel casts kABPersonPhoneIPhoneLabel to the correct toll free bridged type, see RHPersonLabels.h 106 | mutablePhoneMultiValue addValue:@"+14086655555" withLabel:RHPersonPhoneIPhoneLabel]; 107 | person.phonenumbers = mutablePhoneMultiValue; 108 | [person save]; 109 | ``` 110 | Creating a new person. 111 | 112 | ```objectivec 113 | RHPerson *newPerson = [[ab newPersonInDefaultSource] autorelease]; //added to ab 114 | RHPerson *newPerson2 = [[[RHPerson newPersonInSource:[ab defaultSource]] autorelease]; //not added to ab 115 | [ab addPerson:newPerson2]; 116 | NSError* error = nil; 117 | if (![ab save:&error]) NSLog(@"error saving: %@", error); 118 | ``` 119 | Getting an RHPerson object for an ABRecordRef for editing. (note: RHPerson might not be associated with the same addressbook as the original ABRecordRef) 120 | 121 | ```objectivec 122 | ABRecordRef personRef = ...; 123 | RHPerson *person = [ab personForRecordRef:personRef]; 124 | if(person){ 125 | person.firstName = @"Paul"; 126 | person.lastName = @"Frank"; 127 | [person save]; 128 | } 129 | ``` 130 | Presenting / editing an RHPerson instance in a ABPersonViewController. 131 | 132 | ```objectivec 133 | ABPersonViewController *personViewController = [[[ABPersonViewController alloc] init] autorelease]; 134 | 135 | //setup (tell the view controller to use our underlying address book instance, so our person object is directly updated on our behalf) 136 | [person.addressBook performAddressBookAction:^(ABAddressBookRef addressBookRef) { 137 | personViewController.addressBook =addressBookRef; 138 | } waitUntilDone:YES]; 139 | 140 | personViewController.displayedPerson = person.recordRef; 141 | personViewController.allowsEditing = YES; 142 | 143 | [self.navigationController pushViewController:personViewController animated:YES]; 144 | ``` 145 | Background geocoding 146 | 147 | ```objectivec 148 | if ([RHAddressBook isGeocodingSupported){ 149 | [RHAddressBook setPreemptiveGeocodingEnabled:YES]; //class method 150 | } 151 | float progress = [_addressBook preemptiveGeocodingProgress]; // 0.0f - 1.0f 152 | ``` 153 | Geocoding results for a person. 154 | 155 | ```objectivec 156 | CLLocation *location = [person locationForAddressID:0]; 157 | CLPlacemark *placemark = [person placemarkForAddressID:0]; 158 | ``` 159 | 160 | Finding people within distance of a location. 161 | 162 | ```objectivec 163 | NSArray *inRangePeople = [ab peopleWithinDistance:5000 ofLocation:location]; 164 | NSLog(@"people:%@", inRangePeople); 165 | ``` 166 | 167 | Saving. (all of the below are equivalent) 168 | 169 | ```objectivec 170 | BOOL changes = [ab hasUnsavedChanges]; 171 | BOOL result = [ab save]; 172 | BOOL result =[source save]; 173 | BOOL result =[group save]; 174 | BOOL result =[person save]; 175 | ``` 176 | Reverting changes on objects. (reverts the entire addressbook instance, not just the object revert is called on.) 177 | 178 | ```objectivec 179 | [ab revert]; 180 | [source revert]; 181 | [group revert]; 182 | [person revert]; 183 | ``` 184 | Remember, save often in order to avoid painful save conflicts. 185 | 186 | ## Installing 187 | For instructions on how to get started using this static library see [Using Static iOS Libraries](http://rheard.com/blog/using-static-ios-libraries/) at [rheard.com](http://rheard.com). 188 | 189 | ## Licence 190 | Released under the Modified BSD License. 191 | (Attribution Required) 192 |
193 | RHAddressBook
194 | 
195 | Copyright (c) 2011-2012 Richard Heard. All rights reserved.
196 | 
197 | Redistribution and use in source and binary forms, with or without
198 | modification, are permitted provided that the following conditions
199 | are met:
200 | 1. Redistributions of source code must retain the above copyright
201 | notice, this list of conditions and the following disclaimer.
202 | 2. Redistributions in binary form must reproduce the above copyright
203 | notice, this list of conditions and the following disclaimer in the
204 | documentation and/or other materials provided with the distribution.
205 | 3. The name of the author may not be used to endorse or promote products
206 | derived from this software without specific prior written permission.
207 | 
208 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
209 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
210 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
211 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
212 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
213 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
214 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
215 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
216 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
217 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
218 | 
219 | 220 | 221 | ### iOS Version Support (Executive Summary: Supports iOS 4+, tested on iOS 4.0 - 7.1) 222 | This Framework code runs and compiles on and has been tested all the way back to iOS 4.0. 223 | 224 | Unit tests are in place that run on all versions between 4.0 and 7.1. 225 | 226 | Various methods are not available when linking against older SDKs and will return nil when running on older os versions. 227 | eg. Geocoding is only supported on iOS 5+. You should always use the +[RHAddressBook isGeocodingAvailable] method to check whether geocoding is available before attempting to access geocode information. Methods will however, if available safely return nil / empty arrays. 228 | -------------------------------------------------------------------------------- /RHAddressBook/RHAddressBookGeoResult.m: -------------------------------------------------------------------------------- 1 | // 2 | // RHAddressBookGeoResult.m 3 | // RHAddressBook 4 | // 5 | // Created by Richard Heard on 12/11/11. 6 | // Copyright (c) 2011 Richard Heard. All rights reserved. 7 | // 8 | // Redistribution and use in source and binary forms, with or without 9 | // modification, are permitted provided that the following conditions 10 | // are met: 11 | // 1. Redistributions of source code must retain the above copyright 12 | // notice, this list of conditions and the following disclaimer. 13 | // 2. Redistributions in binary form must reproduce the above copyright 14 | // notice, this list of conditions and the following disclaimer in the 15 | // documentation and/or other materials provided with the distribution. 16 | // 3. The name of the author may not be used to endorse or promote products 17 | // derived from this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | // 30 | 31 | #import "RHAddressBookGeoResult.h" 32 | 33 | #if RH_AB_INCLUDE_GEOCODING 34 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 35 | 36 | #import //for hashing functions 37 | #import //for geo 38 | #import "RHAddressBook.h" // for logging 39 | #import "RHAddressBookSharedServices.h" // for isGeocodingSupported 40 | 41 | @interface RHAddressBookGeoResult () 42 | @property (readwrite, retain) CLPlacemark *placemark; 43 | @property (readwrite, assign) BOOL resultNotFound; 44 | @end 45 | 46 | @implementation RHAddressBookGeoResult { 47 | 48 | CLGeocoder *_geocoder; //only a valid pointer while performing a geo operation 49 | } 50 | 51 | @synthesize placemark=_placemark; 52 | @synthesize personID=_personID; 53 | @synthesize addressID=_addressID; 54 | @synthesize addressHash=_addressHash; 55 | @synthesize resultNotFound=_resultNotFound; 56 | 57 | -(CLLocation*)location{ 58 | return _placemark.location; 59 | } 60 | 61 | -(instancetype)init { 62 | [NSException raise:NSInvalidArgumentException format:@"Unable to create a GeoResult without a personID and addressID."]; 63 | return nil; 64 | } 65 | 66 | -(instancetype)initWithPersonID:(ABRecordID)personID addressID:(ABMultiValueIdentifier)addressID { 67 | self = [super init]; 68 | if (self) { 69 | _personID = personID; 70 | _addressID = addressID; 71 | 72 | //compute address hash and store it 73 | _addressHash = arc_retain([RHAddressBookGeoResult hashForDictionary:[self associatedAddressDictionary]]); 74 | } 75 | return self; 76 | } 77 | 78 | 79 | -(instancetype)initWithCoder:(NSCoder *)coder { 80 | self = [super init]; 81 | if (self) { 82 | _placemark = arc_retain([coder decodeObjectForKey:@"placemark"]); 83 | _personID = [coder decodeInt32ForKey:@"personID"]; 84 | _addressID = [coder decodeInt32ForKey:@"addressID"]; 85 | _addressHash = arc_retain([coder decodeObjectForKey:@"addressHash"]); 86 | _resultNotFound = [coder decodeBoolForKey:@"resultNotFound"]; 87 | } 88 | return self; 89 | } 90 | 91 | -(void)encodeWithCoder:(NSCoder *)coder{ 92 | [coder encodeObject:_placemark forKey:@"placemark"]; 93 | [coder encodeInt32:_personID forKey:@"personID"]; 94 | [coder encodeInt32:_addressID forKey:@"addressID"]; 95 | [coder encodeObject:_addressHash forKey:@"addressHash"]; 96 | [coder encodeBool:_resultNotFound forKey:@"resultNotFound"]; 97 | } 98 | 99 | 100 | 101 | -(BOOL)isValid{ 102 | BOOL valid = NO; 103 | 104 | NSDictionary *address = [self associatedAddressDictionary]; 105 | if (address){ 106 | NSString *newHash = [RHAddressBookGeoResult hashForDictionary:address]; 107 | if ([newHash isEqualToString:self.addressHash]){ 108 | valid = YES; 109 | } 110 | } 111 | 112 | return valid; 113 | } 114 | 115 | 116 | -(NSDictionary*)associatedAddressDictionary{ 117 | 118 | NSDictionary *result = nil; 119 | ABAddressBookRef addressBookRef = NULL; 120 | 121 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 60000 122 | if (ABAddressBookCreateWithOptions != NULL){ 123 | 124 | CFErrorRef errorRef = NULL; 125 | addressBookRef = ABAddressBookCreateWithOptions(nil, &errorRef); 126 | 127 | if (!addressBookRef){ 128 | //bail 129 | RHErrorLog(@"Error: Failed to get -[RHAddressBookGeoResult associatedAddressDictionary]. Underlying ABAddressBookCreateWithOptions() failed with error: %@", errorRef); 130 | if (errorRef) CFRelease(errorRef); 131 | 132 | return nil; 133 | } 134 | 135 | } else { 136 | #endif //end iOS6+ 137 | 138 | #pragma clang diagnostic push 139 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 140 | addressBookRef = ABAddressBookCreate(); 141 | #pragma clang diagnostic pop 142 | 143 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 60000 144 | } 145 | #endif //end iOS6+ 146 | 147 | ABRecordRef person = ABAddressBookGetPersonWithRecordID(addressBookRef, self.personID); 148 | if (person){ 149 | ABMultiValueRef addresses = ABRecordCopyValue(person, kABPersonAddressProperty); 150 | if (ABMultiValueGetCount(addresses) > 0){ 151 | 152 | CFIndex index = ABMultiValueGetIndexForIdentifier(addresses, self.addressID); 153 | if (index != -1){ 154 | CFDictionaryRef address = ABMultiValueCopyValueAtIndex(addresses, index); 155 | if (address){ 156 | 157 | result = [[NSDictionary alloc] initWithDictionary:(__bridge NSDictionary*)address]; 158 | CFRelease(address); 159 | } 160 | } else { 161 | //invalid addressID 162 | RHLog(@"got a -1 address index for %@", self); 163 | } 164 | } 165 | //cleanup 166 | if (addresses) CFRelease(addresses); 167 | } 168 | if (addressBookRef) CFRelease(addressBookRef); 169 | 170 | return arc_autorelease(result); 171 | } 172 | 173 | 174 | #pragma mark - geocode 175 | -(void)geocodeAssociatedAddressDictionary{ 176 | 177 | //if geocoding is not supported, do nothing 178 | if (![RHAddressBookSharedServices isGeocodingSupported]) return; 179 | 180 | //don't do anything if our address is no longer valid 181 | if (! [self isValid]){ 182 | RHLog(@"%@ is no longer valid. Skipping Geocode Op.", self); 183 | return; 184 | } 185 | 186 | //geocode currently in progress.. nothing to do 187 | if (_geocoder){ 188 | return; 189 | } 190 | 191 | dispatch_async(dispatch_get_main_queue(), ^{ 192 | _geocoder = [[CLGeocoder alloc] init]; 193 | 194 | NSDictionary *addressDict = [self associatedAddressDictionary]; 195 | 196 | RHLog(@"beginning geocode for :%@", addressDict); 197 | 198 | [_geocoder geocodeAddressDictionary:addressDict completionHandler:^(NSArray *placemarks, NSError *error) { 199 | if ([placemarks count]){ 200 | self.placemark = [placemarks objectAtIndex:0]; 201 | self.resultNotFound = NO; 202 | 203 | RHLog(@"geocode found for :%@", self); 204 | 205 | } else { 206 | if (error.code == kCLErrorNetwork){ 207 | //network error, offline 208 | RHLog(@"geocode not found for: %@. A network error occurred: %@.", self, error); 209 | } else { 210 | //we are interested in: 211 | //kCLErrorGeocodeFoundNoResult 212 | //kCLErrorGeocodeFoundPartialResult 213 | //kCLErrorGeocodeCanceled 214 | self.resultNotFound = YES; 215 | RHLog(@"geocode not found for: %@ error: %@", self, error); 216 | } 217 | } 218 | 219 | // we no longer need the geocoder, release it. 220 | arc_release_nil(_geocoder); 221 | 222 | dispatch_async(dispatch_get_main_queue(), ^{ 223 | //send our notification RHAddressBookPersonAddressGeocodeCompleted 224 | NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys: 225 | [NSNumber numberWithInteger:self.personID], @"personID", 226 | [NSNumber numberWithInteger:self.addressID], @"addressID", 227 | nil]; 228 | [[NSNotificationCenter defaultCenter] postNotificationName:RHAddressBookPersonAddressGeocodeCompleted object:nil userInfo:info]; 229 | 230 | }); 231 | }]; 232 | 233 | }); 234 | 235 | } 236 | 237 | -(void)dealloc{ 238 | arc_release_nil(_placemark); 239 | arc_release_nil(_addressHash); 240 | arc_super_dealloc(); 241 | } 242 | 243 | #pragma mark - hashing 244 | +(NSString*)hashForDictionary:(NSDictionary*)dict{ 245 | return [RHAddressBookGeoResult hashForString:[dict description]]; 246 | } 247 | 248 | +(NSString*)hashForString:(NSString*)string{ 249 | if (! string) return nil; 250 | if (!CC_MD5) return nil; //availability check 251 | 252 | //md5 hash the string 253 | const char *str = [string UTF8String]; 254 | unsigned char outBuffer[CC_MD5_DIGEST_LENGTH]; 255 | CC_MD5(str, strlen(str), outBuffer); 256 | 257 | NSMutableString *hash = [NSMutableString string]; 258 | for(int i = 0; i 32 | #import 33 | 34 | //enable framework debug logging (by default, enabled if DEBUG is defined, change FALSE to TRUE to enable always) 35 | #ifndef RH_AB_ENABLE_DEBUG_LOGGING 36 | #define RH_AB_ENABLE_DEBUG_LOGGING ( defined(DEBUG) || FALSE ) 37 | #endif 38 | 39 | //include geocoding support in RHAddressbook. (0 == NO; 1 == YES;) 40 | #ifndef RH_AB_INCLUDE_GEOCODING 41 | #define RH_AB_INCLUDE_GEOCODING 0 42 | #endif 43 | 44 | //support building with older sdks that don't define NS_DESIGNATED_INITIALIZER 45 | #ifndef NS_DESIGNATED_INITIALIZER 46 | #if __has_attribute(objc_designated_initializer) 47 | #define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) 48 | #else 49 | #define NS_DESIGNATED_INITIALIZER 50 | #endif 51 | #endif 52 | 53 | @class RHRecord; 54 | @class RHSource; 55 | 56 | @class RHPerson; 57 | @class RHGroup; 58 | 59 | @class CLLocation; 60 | @class CLPlacemark; 61 | 62 | //Notification fired when the address book is changed externally 63 | extern NSString * const RHAddressBookExternalChangeNotification; 64 | 65 | #if RH_AB_INCLUDE_GEOCODING 66 | //notification fired when a person and address pair has been geocoded (info dict contains personID and addressID as [NSNumber integerValue]) 67 | extern NSString * const RHAddressBookPersonAddressGeocodeCompleted; 68 | #endif 69 | 70 | //authorization status enum. 71 | typedef NS_ENUM(NSUInteger, RHAuthorizationStatus) { 72 | RHAuthorizationStatusNotDetermined = 0, 73 | RHAuthorizationStatusRestricted, 74 | RHAuthorizationStatusDenied, 75 | RHAuthorizationStatusAuthorized 76 | }; 77 | 78 | @interface RHAddressBook : NSObject 79 | 80 | -(instancetype)init NS_DESIGNATED_INITIALIZER; //create an instance of the addressbook (iOS6+ may return nil, signifying an access error. Error is logged to console) 81 | 82 | +(RHAuthorizationStatus)authorizationStatus; // pre iOS6+ will always return RHAuthorizationStatusAuthorized 83 | -(void)requestAuthorizationWithCompletion:(void (^)(bool granted, NSError* error))completion; //completion block is always called, you only need to call authorize if ([RHAddressBook authorizatonStatus] != RHAuthorizationStatusAuthorized). Pre, iOS6 completion block is always called with granted=YES. The block is called on an arbitrary queue, so dispatch_async to the main queue for any UI updates. 84 | 85 | //any access to the underlying ABAddressBook should be done inside this block wrapper below. 86 | //from the addressbook programming guide... Important: Instances of ABAddressBookRef cannot be used by multiple threads. Each thread must make its own instance by calling ABAddressBookCreate. 87 | -(void)performAddressBookAction:(void (^)(ABAddressBookRef addressBookRef))actionBlock waitUntilDone:(BOOL)wait; 88 | 89 | //access 90 | @property (nonatomic, readonly, copy) NSArray *sources; 91 | @property (nonatomic, readonly, retain) RHSource *defaultSource; 92 | -(RHSource*)sourceForABRecordRef:(ABRecordRef)sourceRef; //returns nil if ref not found in the current ab, eg unsaved record from another ab. if the passed recordRef does not belong to the current addressbook, the returned person objects underlying personRef will differ from the passed in value. This is required in-order to maintain thread safety for the underlying AddressBook instance. 93 | -(RHSource*)sourceForABRecordID:(ABRecordID)sourceID; //returns nil if not found in the current ab, eg unsaved record from another ab. 94 | 95 | @property (nonatomic, readonly, copy) NSArray *groups; 96 | @property (nonatomic, readonly) NSUInteger numberOfGroups; 97 | -(NSArray*)groupsInSource:(RHSource*)source; 98 | -(RHGroup*)groupForABRecordRef:(ABRecordRef)groupRef; //returns nil if ref not found in the current ab, eg unsaved record from another ab. if the passed recordRef does not belong to the current addressbook, the returned person objects underlying personRef will differ from the passed in value. This is required in-order to maintain thread safety for the underlying AddressBook instance. 99 | -(RHGroup*)groupForABRecordID:(ABRecordID)groupID; //returns nil if not found in the current ab, eg unsaved record from another ab. 100 | 101 | @property (nonatomic, readonly, copy) NSArray *people; 102 | @property (nonatomic, readonly) NSUInteger numberOfPeople; 103 | -(NSArray*)peopleOrderedBySortOrdering:(ABPersonSortOrdering)ordering; 104 | @property (nonatomic, readonly, copy) NSArray *peopleOrderedByUsersPreference; //preferred 105 | @property (nonatomic, readonly, copy) NSArray *peopleOrderedByFirstName; 106 | @property (nonatomic, readonly, copy) NSArray *peopleOrderedByLastName; 107 | 108 | -(NSArray*)peopleWithName:(NSString*)name; 109 | -(NSArray*)peopleWithEmail:(NSString*)email; 110 | -(RHPerson*)personForABRecordRef:(ABRecordRef)personRef; //returns nil if ref not found in the current ab, eg unsaved record from another ab. if the passed recordRef does not belong to the current addressbook, the returned person objects underlying personRef will differ from the passed in value. This is required in-order to maintain thread safety for the underlying AddressBook instance. 111 | -(RHPerson*)personForABRecordID:(ABRecordID)personID; //returns nil if not found in the current ab, eg unsaved record from another ab. 112 | 113 | 114 | //add 115 | 116 | //convenience people methods (return a +1 retain count object and are automatically added to the current addressBook) 117 | -(RHPerson*)newPersonInDefaultSource; //returns nil on error (eg read only source) 118 | -(RHPerson*)newPersonInSource:(RHSource*)source; 119 | 120 | //add a person to the current address book instance (this will thrown an exception if the RHPerson object belongs to another ab, eg by being been added to another ab, or created with a source object that was not from the current addressbook) 121 | -(BOOL)addPerson:(RHPerson*)person; 122 | -(BOOL)addPerson:(RHPerson*)person error:(NSError**)error; 123 | 124 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 125 | //add people from vCard to the current addressbook (iOS5+ : pre iOS5 these methods are no-ops) 126 | -(NSArray*)addPeopleFromVCardRepresentationToDefaultSource:(NSData*)representation; //returns an array of newly created RHPerson objects, nil on error 127 | -(NSArray*)addPeopleFromVCardRepresentation:(NSData*)representation toSource:(RHSource*)source; 128 | -(NSData*)vCardRepresentationForPeople:(NSArray*)people; 129 | 130 | #endif //end iOS5+ 131 | 132 | //convenience group methods (return a +1 retain count object and are automatically added to the current addressBook) 133 | -(RHGroup*)newGroupInDefaultSource; //returns nil on error (eg read only source or does not support groups ex. exchange) 134 | -(RHGroup*)newGroupInSource:(RHSource*)source; 135 | 136 | //add a group to the current address book instance (this will thrown an exception if the RHGroup object belongs to another ab, eg by being been added to another ab, or created with a source object that was not from the current addressbook) 137 | -(BOOL)addGroup:(RHGroup*)group; 138 | -(BOOL)addGroup:(RHGroup *)group error:(NSError**)error; 139 | 140 | //remove 141 | -(BOOL)removePerson:(RHPerson*)person; 142 | -(BOOL)removePerson:(RHPerson*)person error:(NSError**)error; 143 | 144 | -(BOOL)removeGroup:(RHGroup*)group; 145 | -(BOOL)removeGroup:(RHGroup*)group error:(NSError**)error; 146 | 147 | 148 | //save 149 | -(BOOL)save; 150 | -(BOOL)saveWithError:(NSError**)error; 151 | @property (nonatomic, readonly) BOOL hasUnsavedChanges; 152 | -(void)revert; 153 | 154 | 155 | //user prefs 156 | +(ABPersonSortOrdering)sortOrdering; 157 | +(BOOL)orderByFirstName; // YES if first name ordering is preferred 158 | +(BOOL)orderByLastName; // YES if last name ordering is preferred 159 | 160 | +(ABPersonCompositeNameFormat)compositeNameFormat; 161 | +(BOOL)compositeNameFormatFirstNameFirst; // YES if first name comes before last name 162 | +(BOOL)compositeNameFormatLastNameFirst; // YES if last name comes before first name 163 | 164 | 165 | #if RH_AB_INCLUDE_GEOCODING 166 | //if geocoding is currently supported (runtime & compile-time check safe) 167 | +(BOOL)isGeocodingSupported; 168 | 169 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 170 | 171 | //geocoding (if geocoding is not available or background processing is disabled, only results already processed will be returned) 172 | 173 | //class methods to enable / disable geocoding 174 | +(BOOL)isPreemptiveGeocodingEnabled; //defaults to YES 175 | +(void)setPreemptiveGeocodingEnabled:(BOOL)enabled; //Geocoding starts on first instantiation of the AB class, therefore this is a class method, allowing you to set it to false before the first AB instance is created. 176 | @property (nonatomic, readonly) float preemptiveGeocodingProgress; // returns percentage range 0.0f - 1.0f 177 | 178 | //forward 179 | -(CLPlacemark*)placemarkForPerson:(RHPerson*)person addressID:(ABMultiValueIdentifier)addressID; 180 | -(CLLocation*)locationForPerson:(RHPerson*)person addressID:(ABMultiValueIdentifier)addressID; 181 | 182 | //reverse 183 | -(NSArray*)peopleWithinDistance:(double)distance ofLocation:(CLLocation*)location; //distance in meters 184 | -(RHPerson*)personClosestToLocation:(CLLocation*)location; 185 | -(RHPerson*)personClosestToLocation:(CLLocation*)location distanceOut:(double*)distanceOut; //distance in meters 186 | 187 | #endif //end iOS5+ 188 | #endif //end Geocoding 189 | 190 | @end 191 | 192 | 193 | //define the debug logging macros 194 | 195 | #if RH_AB_ENABLE_DEBUG_LOGGING 196 | #define RHLog(format, ...) NSLog( @"%s:%i %@ ", __PRETTY_FUNCTION__, __LINE__, [NSString stringWithFormat: format, ##__VA_ARGS__]) 197 | #else 198 | #define RHLog(format, ...) 199 | #endif 200 | 201 | #define RHErrorLog(format, ...) NSLog( @"%s:%i %@ ", __PRETTY_FUNCTION__, __LINE__, [NSString stringWithFormat: format, ##__VA_ARGS__]) 202 | 203 | -------------------------------------------------------------------------------- /RHAddressBook/RHPerson.h: -------------------------------------------------------------------------------- 1 | // 2 | // RHPerson.h 3 | // RHAddressBook 4 | // 5 | // Created by Richard Heard on 14/11/11. 6 | // Copyright (c) 2011 Richard Heard. All rights reserved. 7 | // 8 | // Redistribution and use in source and binary forms, with or without 9 | // modification, are permitted provided that the following conditions 10 | // are met: 11 | // 1. Redistributions of source code must retain the above copyright 12 | // notice, this list of conditions and the following disclaimer. 13 | // 2. Redistributions in binary form must reproduce the above copyright 14 | // notice, this list of conditions and the following disclaimer in the 15 | // documentation and/or other materials provided with the distribution. 16 | // 3. The name of the author may not be used to endorse or promote products 17 | // derived from this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | // 30 | 31 | #import "RHRecord.h" 32 | 33 | #import 34 | #import "RHMultiValue.h" 35 | #import "RHPersonLabels.h" 36 | 37 | @class RHPerson; 38 | @class RHSource; 39 | 40 | @class CLPlacemark; 41 | @class CLLocation; 42 | 43 | // To create a new empty instance of a person either use -[RHAddressBook newPersonInDefaultSource] or the below newPersonInSource: method 44 | // If you have an existing ABPersonRef that you want to wrap with an RHPerson object, use the personForABRecordRef: method on RHAddressBook or the below forwarding wrapper method. 45 | 46 | @interface RHPerson : RHRecord 47 | 48 | //once a person object is created using a given source object from an ab instance, its not safe to use that object with any other instance of the addressbook. 49 | //you can always access the persons associated addressbook object using @property (readonly) RHAddressBook* addressBook; 50 | //the addressbook instance is guaranteed to stay alive until its last associated object is dealloc'd. 51 | //these methods do not automatically add the new object to the source.addressBook, if you want it added you will need do add it yourself. -[RHAddressBook addPerson:]; 52 | +(instancetype)newPersonInSource:(RHSource*)source; 53 | -(instancetype)initWithSource:(RHSource*)source; 54 | 55 | //look up an RHPerson instance for an existing ABRecordRef in a particular addressbook; if the current recordRef does not belong to the given addressbook, the person objects underlying personRef will differ from the passed in value. This is required in-order to maintain thread safety for the underlying AddressBook instance. 56 | +(RHPerson*)personForABRecordRef:(ABRecordRef)personRef inAddressBook:(RHAddressBook*)addressBook; //equivalent to -[RHAddressBook personForABRecordRef:]; 57 | +(RHPerson*)personForABRecordID:(ABRecordID)personID inAddressBook:(RHAddressBook*)addressBook; //equivalent to -[RHAddressBook personForABRecordID:]; 58 | 59 | 60 | //localised property and labels (class methods) 61 | +(NSString*)localizedPropertyName:(ABPropertyID)propertyID; //properties eg:kABPersonFirstNameProperty (ABPersonCopyLocalizedPropertyName) 62 | +(NSString*)localizedLabel:(NSString*)label; //labels eg: kABWorkLabel (ABAddressBookCopyLocalizedLabel) 63 | 64 | 65 | //person is from given source 66 | @property (nonatomic, readonly, weak) RHSource *inSource; 67 | 68 | //linked people (ie other cards that represent the same person in other sources) 69 | @property (nonatomic, readonly, copy) NSArray *linkedPeople; 70 | 71 | //image 72 | 73 | #if __IPHONE_OS_VERSION_MAX_ALLOWED < 40100 74 | //iOS4.1 added ABPersonImageFormat, however later versions of the headers think it was added in 4.0 75 | //running on 4.0 we will always return the full size image 76 | typedef enum { 77 | kABPersonImageFormatThumbnail = 0, // the square thumbnail 78 | kABPersonImageFormatOriginalSize = 2 // the original image as set by ABPersonSetImageData 79 | } ABPersonImageFormat; 80 | #endif 81 | 82 | 83 | @property (nonatomic, readonly) BOOL hasImage; 84 | @property (nonatomic, readonly, copy) UIImage *thumbnail; 85 | @property (nonatomic, readonly, copy) UIImage *originalImage; 86 | -(UIImage*)imageWithFormat:(ABPersonImageFormat)imageFormat; 87 | @property (nonatomic, readonly, copy) NSData *thumbnailData; 88 | @property (nonatomic, readonly, copy) NSData *originalImageData; 89 | -(NSData*)imageDataWithFormat:(ABPersonImageFormat)imageFormat; 90 | -(BOOL)setImage:(UIImage*)image; 91 | -(BOOL)removeImage; 92 | 93 | //personal properties 94 | @property (nonatomic, copy, readonly) NSString *name; // alias for compositeName 95 | @property (nonatomic, copy) NSString *firstName; // kABPersonFirstNameProperty 96 | @property (nonatomic, copy) NSString *lastName; // kABPersonLastNameProperty 97 | @property (nonatomic, copy) NSString *middleName; // kABPersonMiddleNameProperty 98 | @property (nonatomic, copy) NSString *prefix; // kABPersonPrefixProperty 99 | @property (nonatomic, copy) NSString *suffix; // kABPersonSuffixProperty 100 | @property (nonatomic, copy) NSString *nickname; // kABPersonNicknameProperty 101 | 102 | @property (nonatomic, copy) NSString *firstNamePhonetic; // kABPersonFirstNamePhoneticProperty 103 | @property (nonatomic, copy) NSString *lastNamePhonetic; // kABPersonLastNamePhoneticProperty 104 | @property (nonatomic, copy) NSString *middleNamePhonetic; // kABPersonMiddleNamePhoneticProperty 105 | 106 | @property (nonatomic, copy) NSString *organization; // kABPersonOrganizationProperty 107 | @property (nonatomic, copy) NSString *jobTitle; // kABPersonJobTitleProperty 108 | @property (nonatomic, copy) NSString *department; // kABPersonDepartmentProperty 109 | 110 | @property (nonatomic, copy) RHMultiStringValue *emails; // kABPersonEmailProperty - (Multi String) 111 | @property (nonatomic, copy) NSDate *birthday; // kABPersonBirthdayProperty 112 | @property (nonatomic, copy) NSString *note; // kABPersonNoteProperty 113 | 114 | @property (nonatomic, copy, readonly) NSDate *created; // kABPersonCreationDateProperty 115 | @property (nonatomic, copy, readonly) NSDate *modified; // kABPersonModificationDateProperty 116 | 117 | // (For more info on the keys and values for MultiValue objects check out ) 118 | // (Also check out RHPersonLabels.h, it casts a bunch of CF labels into their toll free bridged counterparts for ease of use with this class ) 119 | 120 | //Addresses 121 | @property (nonatomic, copy) RHMultiDictionaryValue *addresses; // kABPersonAddressProperty - (Multi Dictionary) dictionary keys are ( kABPersonAddressStreetKey, kABPersonAddressCityKey, kABPersonAddressStateKey, kABPersonAddressZIPKey, kABPersonAddressCountryKey, kABPersonAddressCountryCodeKey ) 122 | 123 | 124 | //Dates 125 | @property (nonatomic, copy) RHMultiDateTimeValue *dates; // kABPersonDateProperty - (Multi Date) possible predefined labels ( kABPersonAnniversaryLabel ) 126 | 127 | //Kind 128 | @property (nonatomic, copy) NSNumber *kind; // kABPersonKindProperty (Integer) possible values include (kABPersonKindPerson, kABPersonKindOrganization) 129 | -(BOOL)isOrganization; // if person == kABPersonKindOrganization 130 | -(BOOL)isPerson; // if person == kABPersonKindPerson 131 | 132 | //Phone numbers 133 | @property (nonatomic, copy) RHMultiStringValue *phoneNumbers; // kABPersonPhoneProperty (Multi String) possible labels are ( kABPersonPhoneMobileLabel, kABPersonPhoneIPhoneLabel, kABPersonPhoneMainLabel, kABPersonPhoneHomeFAXLabel, kABPersonPhoneWorkFAXLabel, kABPersonPhoneOtherFAXLabel, kABPersonPhonePagerLabel ) 134 | 135 | 136 | //IM 137 | @property (nonatomic, copy) RHMultiDictionaryValue *instantMessageServices; // kABPersonInstantMessageProperty - (Multi Dictionary) dictionary keys are ( kABPersonInstantMessageServiceKey, kABPersonInstantMessageUsernameKey ) possible services are ( kABPersonInstantMessageServiceYahoo, kABPersonInstantMessageServiceJabber, kABPersonInstantMessageServiceMSN, kABPersonInstantMessageServiceICQ, kABPersonInstantMessageServiceAIM, kABPersonInstantMessageServiceQQ, kABPersonInstantMessageServiceGoogleTalk, kABPersonInstantMessageServiceSkype, kABPersonInstantMessageServiceFacebook, kABPersonInstantMessageServiceGaduGadu ) 138 | 139 | 140 | //URLs 141 | @property (nonatomic, copy) RHMultiStringValue *urls; // kABPersonURLProperty - (Multi String) possible labels are ( kABPersonHomePageLabel ) 142 | 143 | 144 | //Related Names (Relationships) 145 | @property (nonatomic, copy) RHMultiStringValue *relatedNames; // kABPersonRelatedNamesProperty - (Multi String) possible labels are ( kABPersonFatherLabel, kABPersonMotherLabel, kABPersonParentLabel, kABPersonBrotherLabel, kABPersonSisterLabel, kABPersonChildLabel, kABPersonFriendLabel, kABPersonSpouseLabel, kABPersonPartnerLabel, kABPersonAssistantLabel, kABPersonManagerLabel ) 146 | 147 | 148 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 149 | 150 | //Social Profile (iOS5 +) 151 | @property (nonatomic, copy) RHMultiDictionaryValue *socialProfiles; // kABPersonSocialProfileProperty - (Multi Dictionary) possible dictionary keys are ( kABPersonSocialProfileURLKey, kABPersonSocialProfileServiceKey, kABPersonSocialProfileUsernameKey, kABPersonSocialProfileUserIdentifierKey ) 152 | // possible kABPersonSocialProfileServiceKey values ( kABPersonSocialProfileServiceTwitter, kABPersonSocialProfileServiceGameCenter, kABPersonSocialProfileService Facebook, kABPersonSocialProfileServiceMyspace, kABPersonSocialProfileServiceLinkedIn, kABPersonSocialProfileServiceFlickr ) 153 | 154 | //vCard formatting (iOS5 +) 155 | -(NSData*)vCardRepresentation; //the current persons vCard representation 156 | +(NSData*)vCardRepresentationForPeople:(NSArray*)people; //array of RHPerson Objects. 157 | 158 | //geocoding 159 | #if RH_AB_INCLUDE_GEOCODING 160 | -(CLPlacemark*)placemarkForAddressID:(ABMultiValueIdentifier)addressID; 161 | -(CLLocation*)locationForAddressID:(ABMultiValueIdentifier)addressID; 162 | #endif //end Geocoding 163 | 164 | #endif //end iOS5+ 165 | 166 | //remove person from addressBook 167 | -(BOOL)remove; 168 | @property (nonatomic, readonly) BOOL hasBeenRemoved; // we check to see if ABAddressBookGetPersonWithRecordID() returns NULL for self.recordID; This is the recommended approach from the AB docs. 169 | 170 | 171 | //composite name format for this explicit record 172 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 173 | -(ABPersonCompositeNameFormat)compositeNameFormat; // at runtime, if you are running on a pre ios 7 device, we return the default system preference 174 | #endif //end iOS7+ 175 | 176 | @end 177 | -------------------------------------------------------------------------------- /RHAddressBook/RHRecord.m: -------------------------------------------------------------------------------- 1 | // 2 | // RHRecord.m 3 | // RHAddressBook 4 | // 5 | // Created by Richard Heard on 11/11/11. 6 | // Copyright (c) 2011 Richard Heard. All rights reserved. 7 | // 8 | // Redistribution and use in source and binary forms, with or without 9 | // modification, are permitted provided that the following conditions 10 | // are met: 11 | // 1. Redistributions of source code must retain the above copyright 12 | // notice, this list of conditions and the following disclaimer. 13 | // 2. Redistributions in binary form must reproduce the above copyright 14 | // notice, this list of conditions and the following disclaimer in the 15 | // documentation and/or other materials provided with the distribution. 16 | // 3. The name of the author may not be used to endorse or promote products 17 | // derived from this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | // 30 | 31 | #import "RHRecord.h" 32 | #import "RHRecord_Private.h" 33 | 34 | #import "RHAddressBook.h" 35 | #import "RHAddressBook_private.h" 36 | #import "RHMultiValue.h" 37 | 38 | @implementation RHRecord 39 | 40 | -(instancetype)initWithAddressBook:(RHAddressBook*)addressBook recordRef:(ABRecordRef)recordRef{ 41 | self = [super init]; 42 | if (self) { 43 | 44 | if (!recordRef){ 45 | arc_release_nil(self); 46 | return nil; 47 | } 48 | 49 | _addressBook = arc_retain(addressBook); 50 | _recordRef = CFRetain(recordRef); 51 | 52 | //check in so we can be added to the weak link cache 53 | if (_addressBook){ 54 | [_addressBook _recordCheckIn:self]; 55 | } 56 | } 57 | return self; 58 | } 59 | #pragma mark - thread safe action block 60 | -(void)performRecordAction:(void (^)(ABRecordRef recordRef))actionBlock waitUntilDone:(BOOL)wait{ 61 | //if we have an address book perform it on that thread 62 | if (_addressBook){ 63 | if (_recordRef) CFRetain(_recordRef); 64 | [_addressBook performAddressBookAction:^(ABAddressBookRef addressBookRef) { 65 | actionBlock(_recordRef); 66 | if (_recordRef) CFRelease(_recordRef); 67 | } waitUntilDone:wait]; 68 | } else { 69 | //otherwise, a user created object... just use current thread. 70 | actionBlock(_recordRef); 71 | } 72 | 73 | } 74 | 75 | 76 | 77 | #pragma mark - properties 78 | 79 | @synthesize addressBook=_addressBook; 80 | @synthesize recordRef=_recordRef; 81 | 82 | -(ABRecordID)recordID{ 83 | 84 | __block ABRecordID recordID = kABPropertyInvalidID; 85 | 86 | [self performRecordAction:^(ABRecordRef recordRef) { 87 | recordID = ABRecordGetRecordID(recordRef); 88 | } waitUntilDone:YES]; 89 | 90 | return recordID; 91 | } 92 | 93 | -(ABRecordType)recordType{ 94 | 95 | __block ABRecordType recordType = -1; 96 | 97 | [self performRecordAction:^(ABRecordRef recordRef) { 98 | recordType = ABRecordGetRecordType(recordRef); 99 | } waitUntilDone:YES]; 100 | 101 | return recordType; 102 | } 103 | 104 | -(NSString*)compositeName{ 105 | __block CFStringRef compositeNameRef = NULL; 106 | 107 | [self performRecordAction:^(ABRecordRef recordRef) { 108 | compositeNameRef = ABRecordCopyCompositeName(recordRef); 109 | } waitUntilDone:YES]; 110 | 111 | NSString* compositeName = [(__bridge NSString*)compositeNameRef copy]; 112 | if (compositeNameRef) CFRelease(compositeNameRef); 113 | 114 | return arc_autorelease(compositeName); 115 | } 116 | 117 | 118 | #pragma mark - generic getter/setter/remover 119 | -(id)getBasicValueForPropertyID:(ABPropertyID)propertyID{ 120 | if (!_recordRef) return nil; //no record ref 121 | if (propertyID == kABPropertyInvalidID) return nil; //invalid 122 | 123 | __block CFTypeRef value = NULL; 124 | 125 | [self performRecordAction:^(ABRecordRef recordRef) { 126 | value = ABRecordCopyValue(recordRef, propertyID); 127 | } waitUntilDone:YES]; 128 | 129 | id result = [(__bridge id)value copy]; 130 | if (value) CFRelease(value); 131 | 132 | return arc_autorelease(result); 133 | } 134 | 135 | 136 | -(BOOL)setBasicValue:(CFTypeRef)value forPropertyID:(ABPropertyID)propertyID error:(NSError**)error{ 137 | if (!_recordRef) return false; //no record ref 138 | if (propertyID == kABPropertyInvalidID) return false; //invalid 139 | if (value == NULL) return [self unsetBasicValueForPropertyID:propertyID error:error]; //allow NULL to unset the property 140 | 141 | __block CFErrorRef cfError = NULL; 142 | __block BOOL result; 143 | [self performRecordAction:^(ABRecordRef recordRef) { 144 | result = ABRecordSetValue(recordRef, propertyID, value, &cfError); 145 | } waitUntilDone:YES]; 146 | 147 | if (!result){ 148 | if (error && cfError) *error = (NSError*)ARCBridgingRelease(CFRetain(cfError)); 149 | if (cfError) CFRelease(cfError); 150 | } 151 | return result; 152 | } 153 | 154 | -(BOOL)unsetBasicValueForPropertyID:(ABPropertyID)propertyID error:(NSError**)error{ 155 | if (!_recordRef) return false; //no record ref 156 | if (propertyID == kABPropertyInvalidID) return false; //invalid 157 | 158 | __block CFErrorRef cfError = NULL; 159 | __block BOOL result; 160 | [self performRecordAction:^(ABRecordRef recordRef) { 161 | result = ABRecordRemoveValue(recordRef, propertyID, &cfError); 162 | } waitUntilDone:YES]; 163 | 164 | if (!result){ 165 | if (error && cfError) *error = (NSError*)ARCBridgingRelease(CFRetain(cfError)); 166 | if (cfError) CFRelease(cfError); 167 | } 168 | return result; 169 | } 170 | 171 | 172 | #pragma mark - generic multi value getter/setter/remover 173 | -(RHMultiValue*)getMultiValueForPropertyID:(ABPropertyID)propertyID{ 174 | if (!_recordRef) return nil; //no record ref 175 | if (propertyID == kABPropertyInvalidID) return nil; //invalid 176 | 177 | __block ABMultiValueRef valueRef = NULL; 178 | 179 | [self performRecordAction:^(ABRecordRef recordRef) { 180 | valueRef = ABRecordCopyValue(recordRef, propertyID); 181 | } waitUntilDone:YES]; 182 | 183 | RHMultiValue *multiValue = nil; 184 | if (valueRef){ 185 | multiValue = [[RHMultiValue alloc] initWithMultiValueRef:valueRef]; 186 | CFRelease(valueRef); 187 | } 188 | return arc_autorelease(multiValue); 189 | } 190 | 191 | -(BOOL)setMultiValue:(RHMultiValue*)multiValue forPropertyID:(ABPropertyID)propertyID error:(NSError**)error{ 192 | if (multiValue == NULL) return [self unsetMultiValueForPropertyID:propertyID error:error]; //allow NULL to unset the property 193 | return [self setBasicValue:multiValue.multiValueRef forPropertyID:propertyID error:error]; 194 | } 195 | 196 | -(BOOL)unsetMultiValueForPropertyID:(ABPropertyID)propertyID error:(NSError**)error{ 197 | //this should just be able to be forwarded 198 | return [self unsetBasicValueForPropertyID:propertyID error:error]; 199 | } 200 | 201 | 202 | #pragma mark - forward 203 | -(BOOL)save{ 204 | return [_addressBook save]; 205 | } 206 | 207 | //renamed method shim 208 | -(BOOL)save:(NSError**)error{ 209 | RHErrorLog(@"RHAddressBook: The save: method has been renamed to saveWithError: You should update your sources appropriately."); 210 | return [self saveWithError:error]; 211 | } 212 | 213 | -(BOOL)saveWithError:(NSError**)error{ 214 | return [_addressBook saveWithError:error]; 215 | } 216 | -(BOOL)hasUnsavedChanges{ 217 | return [_addressBook hasUnsavedChanges]; 218 | } 219 | -(void)revert{ 220 | [_addressBook revert]; 221 | } 222 | 223 | 224 | #pragma mark - cleanup 225 | 226 | //unfortunately ensuring dealloc occurs on our _addressBook queue is not available under ARC. 227 | #if ARC_IS_NOT_ENABLED 228 | -(oneway void)release{ 229 | //ensure dealloc occurs on our ABs addressBookQueue 230 | //we do this to guarantee that we are removed from the weak cache before someone else ends up with us. 231 | if (_addressBook && !rh_dispatch_is_current_queue_for_addressbook(_addressBook)){ 232 | dispatch_async(_addressBook.addressBookQueue, ^{ 233 | [self release]; 234 | }); 235 | } else { 236 | [super release]; 237 | } 238 | } 239 | #endif 240 | 241 | -(void)dealloc { 242 | 243 | //check out so we can be removed from the weak link lookup cache 244 | if (_addressBook){ 245 | [_addressBook _recordCheckOut:self]; 246 | } 247 | 248 | arc_release_nil(_addressBook); 249 | if (_recordRef) CFRelease(_recordRef); 250 | _recordRef = NULL; 251 | arc_super_dealloc(); 252 | } 253 | 254 | #pragma mark - misc 255 | -(NSString*)description{ 256 | return [NSString stringWithFormat:@"<%@: %p> name:%@", NSStringFromClass([self class]), self, self.compositeName]; 257 | } 258 | 259 | +(NSString*)descriptionForRecordType:(ABRecordType)type{ 260 | switch (type) { 261 | case kABPersonType: return @"kABPersonType - Person Record Type"; 262 | case kABGroupType: return @"kABGroupType - Group Record Type"; 263 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 264 | case kABSourceType: return @"kABSourceType - Source Record Type"; 265 | #endif 266 | default: return @"Unknown Property Type"; 267 | } 268 | } 269 | 270 | +(NSString*)descriptionForPropertyType:(ABPropertyType)type{ 271 | switch (type) { 272 | case kABInvalidPropertyType: return @"kABInvalidPropertyType - Invalid Property Type"; 273 | case kABStringPropertyType: return @"kABStringPropertyType - String Property Type"; 274 | case kABIntegerPropertyType: return @"kABIntegerPropertyType - Integer Property Type"; 275 | case kABRealPropertyType: return @"kABRealPropertyType - Real Property Type"; 276 | case kABDateTimePropertyType: return @"kABDateTimePropertyType - Date Time Property Type"; 277 | case kABDictionaryPropertyType: return @"kABDictionaryPropertyType - Dictionary Property Type"; 278 | 279 | case kABMultiStringPropertyType: return @"kABMultiStringPropertyType - Multi String Property Type"; 280 | case kABMultiIntegerPropertyType: return @"kABMultiIntegerPropertyType - Multi Integer Property Type"; 281 | case kABMultiRealPropertyType: return @"kABMultiRealPropertyType - Multi Real Property Type"; 282 | case kABMultiDateTimePropertyType: return @"kABMultiDateTimePropertyType - Multi Date Time Property Type"; 283 | case kABMultiDictionaryPropertyType: return @"kABMultiDictionaryPropertyType - Multi Dictionary Property Type"; 284 | 285 | default: return @"Unknown Property Type"; 286 | } 287 | } 288 | 289 | 290 | @end 291 | -------------------------------------------------------------------------------- /RHAddressBookTester/RHAddressBookViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // RHAddressBookViewController.m 3 | // RHAddressBook 4 | // 5 | // Created by Richard Heard on 20/02/12. 6 | // Copyright (c) 2012 Richard Heard. All rights reserved. 7 | // 8 | 9 | #import "RHAddressBookViewController.h" 10 | #import "RHGroupViewController.h" 11 | 12 | #import 13 | 14 | @interface RHAddressBookViewController () 15 | 16 | -(void)configureCell:(UITableViewCell*)cell forInfoAtRow:(NSInteger)row; 17 | -(void)configureCell:(UITableViewCell*)cell forLocationAtRow:(NSInteger)row; 18 | -(void)configureCell:(UITableViewCell*)cell forSourceAtRow:(NSInteger)row; 19 | -(void)configureCell:(UITableViewCell*)cell forGroupAtRow:(NSInteger)row; 20 | -(void)configureCell:(UITableViewCell*)cell forPersonAtRow:(NSInteger)row; 21 | 22 | -(void)addNewGroup; 23 | -(void)addNewPerson; 24 | 25 | -(void)addressBookChanged:(NSNotification*)notification; 26 | 27 | 28 | @end 29 | 30 | @implementation RHAddressBookViewController 31 | 32 | @synthesize addressBook=_addressBook; 33 | 34 | - (instancetype)initWithAddressBook:(RHAddressBook *)addressBook { 35 | self = [super initWithStyle:UITableViewStyleGrouped]; 36 | if (self) { 37 | // Custom initialization 38 | self.title = NSLocalizedString(@"RHAddressBook", nil); 39 | _addressBook = [addressBook retain]; 40 | } 41 | return self; 42 | } 43 | 44 | #define RN(x) [x release]; x = nil; 45 | - (void)dealloc{ 46 | RN(_addressBook); 47 | RN(_sources); 48 | RN(_groups); 49 | RN(_people); 50 | 51 | [[NSNotificationCenter defaultCenter] removeObserver:self]; //for the ab externally changed notifications 52 | 53 | [super dealloc]; 54 | } 55 | - (void)viewDidLoad { 56 | [super viewDidLoad]; 57 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(addressBookChanged:) name:RHAddressBookExternalChangeNotification object:nil]; 58 | self.navigationItem.rightBarButtonItem = self.editButtonItem; 59 | 60 | self.tableView.allowsSelectionDuringEditing = YES; 61 | 62 | } 63 | 64 | - (void)viewDidUnload { 65 | [super viewDidUnload]; 66 | 67 | //discard our cached values 68 | RN(_sources); 69 | RN(_groups); 70 | RN(_people); 71 | 72 | [[NSNotificationCenter defaultCenter] removeObserver:self name:RHAddressBookExternalChangeNotification object:nil]; 73 | 74 | } 75 | 76 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { 77 | return (interfaceOrientation == UIInterfaceOrientationPortrait); 78 | } 79 | 80 | - (void)setEditing:(BOOL)editing animated:(BOOL)animated { 81 | [super setEditing:editing animated:animated]; 82 | [self.tableView setEditing:editing animated:animated]; 83 | 84 | NSArray* paths = [NSArray arrayWithObjects: 85 | [NSIndexPath indexPathForRow:[_groups count] inSection:kRHAddressBookViewControllerGroupsSection], 86 | [NSIndexPath indexPathForRow:[_people count] inSection:kRHAddressBookViewControllerPeopleSection], 87 | nil]; 88 | 89 | if(editing) { 90 | [self.tableView insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationFade]; 91 | } else { 92 | [self.tableView deleteRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationFade]; 93 | } 94 | } 95 | 96 | #pragma mark - Table view data source 97 | 98 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 99 | return kRHAddressBookViewControllerNumberOfSections; 100 | } 101 | 102 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 103 | if (section == kRHAddressBookViewControllerInfoSection){ 104 | return kRHAddressBookViewControllerInfoCellsCount; 105 | } else if (section == kRHAddressBookViewControllerLocationSection){ 106 | return kRHAddressBookViewControllerLocationCellsCount; 107 | } else if (section == kRHAddressBookViewControllerSourcesSection){ 108 | [_sources release]; 109 | _sources = [[_addressBook sources] mutableCopy]; 110 | return [_sources count]; 111 | } else if (section == kRHAddressBookViewControllerGroupsSection){ 112 | [_groups release]; 113 | _groups = [[_addressBook groups] mutableCopy]; 114 | return [_groups count] + self.tableView.editing; //to allow for + button 115 | } else if (section == kRHAddressBookViewControllerPeopleSection){ 116 | [_people release]; 117 | _people = [[_addressBook peopleOrderedByUsersPreference] mutableCopy]; 118 | return [_people count] + self.tableView.editing; //to allow for + button 119 | } 120 | return 0; 121 | } 122 | 123 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 124 | static NSString *cellIdentifier = @"RHAddressBookViewControllerCell"; 125 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; 126 | if (!cell){ 127 | cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier] autorelease]; 128 | } 129 | //reset 130 | cell.textLabel.text = nil; 131 | 132 | switch (indexPath.section) { 133 | case kRHAddressBookViewControllerInfoSection: [self configureCell:cell forInfoAtRow:indexPath.row]; break; 134 | case kRHAddressBookViewControllerLocationSection:[self configureCell:cell forLocationAtRow:indexPath.row]; break; 135 | case kRHAddressBookViewControllerSourcesSection: [self configureCell:cell forSourceAtRow:indexPath.row]; break; 136 | case kRHAddressBookViewControllerGroupsSection: [self configureCell:cell forGroupAtRow:indexPath.row]; break; 137 | case kRHAddressBookViewControllerPeopleSection: [self configureCell:cell forPersonAtRow:indexPath.row]; break; 138 | } 139 | 140 | return cell; 141 | } 142 | 143 | -(NSString*)titleForSection:(NSInteger)section { 144 | switch (section) { 145 | case kRHAddressBookViewControllerInfoSection: return NSLocalizedString(@"Info", nil); 146 | case kRHAddressBookViewControllerSourcesSection: return NSLocalizedString(@"Sources", nil); 147 | case kRHAddressBookViewControllerGroupsSection: return NSLocalizedString(@"Groups", nil); 148 | case kRHAddressBookViewControllerPeopleSection: return NSLocalizedString(@"People", nil); 149 | case kRHAddressBookViewControllerLocationSection: return NSLocalizedString(@"Location", nil); 150 | 151 | default: return nil; 152 | } 153 | } 154 | -(NSString*)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{ 155 | return [self titleForSection:section]; 156 | } 157 | 158 | // Override to support conditional editing of the table view. 159 | - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { 160 | 161 | //groups and people can be edited 162 | if (indexPath.section == kRHAddressBookViewControllerGroupsSection) return YES; 163 | if (indexPath.section == kRHAddressBookViewControllerPeopleSection) return YES; 164 | 165 | return NO; 166 | } 167 | 168 | - (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath { 169 | if (indexPath.section == kRHAddressBookViewControllerGroupsSection && indexPath.row >= [_groups count]) return UITableViewCellEditingStyleInsert; 170 | if (indexPath.section == kRHAddressBookViewControllerPeopleSection && indexPath.row >= [_people count]) return UITableViewCellEditingStyleInsert; 171 | 172 | return UITableViewCellEditingStyleDelete; 173 | } 174 | 175 | // Override to support editing the table view. 176 | - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { 177 | if (editingStyle == UITableViewCellEditingStyleDelete) { 178 | if (indexPath.section == kRHAddressBookViewControllerGroupsSection) { 179 | RHGroup *group = [[_addressBook groups] objectAtIndex:indexPath.row]; 180 | [group remove]; 181 | [_addressBook save]; 182 | } else if (indexPath.section == kRHAddressBookViewControllerPeopleSection) { 183 | RHPerson *person = [[_addressBook peopleOrderedByUsersPreference] objectAtIndex:indexPath.row]; 184 | [person remove]; 185 | [_addressBook save]; 186 | } 187 | 188 | [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; 189 | } 190 | else if (editingStyle == UITableViewCellEditingStyleInsert) { 191 | // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view 192 | if (indexPath.section == kRHAddressBookViewControllerGroupsSection) { 193 | [self addNewGroup]; 194 | } else if (indexPath.section == kRHAddressBookViewControllerPeopleSection) { 195 | [self addNewPerson]; 196 | } 197 | } 198 | } 199 | 200 | 201 | #pragma mark - Table view delegate 202 | 203 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 204 | UIViewController *pushController = nil; 205 | 206 | if (indexPath.section == kRHAddressBookViewControllerSourcesSection && indexPath.row < [_sources count]){ 207 | 208 | } else if (indexPath.section == kRHAddressBookViewControllerGroupsSection && indexPath.row < [_groups count]) { 209 | RHGroup *group = [_groups objectAtIndex:indexPath.row]; 210 | pushController = [[RHGroupViewController alloc] initWithGroup:group]; 211 | 212 | } else if (indexPath.section == kRHAddressBookViewControllerPeopleSection && indexPath.row < [_people count]) { 213 | 214 | //TODO: push our own viewer view, for now just use the AB default one. 215 | RHPerson *person = [_people objectAtIndex:indexPath.row]; 216 | 217 | ABPersonViewController *personViewController = [[[ABPersonViewController alloc] init] autorelease]; 218 | 219 | //setup (tell the view controller to use our underlying address book instance, so our person object is directly updated) 220 | [person.addressBook performAddressBookAction:^(ABAddressBookRef addressBookRef) { 221 | personViewController.addressBook =addressBookRef; 222 | } waitUntilDone:YES]; 223 | 224 | personViewController.displayedPerson = person.recordRef; 225 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 226 | personViewController.allowsActions = YES; 227 | #endif 228 | personViewController.allowsEditing = YES; 229 | 230 | 231 | [self.navigationController pushViewController:personViewController animated:YES]; 232 | 233 | } else if (indexPath.section == kRHAddressBookViewControllerLocationSection){ 234 | //toggle location 235 | #if RH_AB_INCLUDE_GEOCODING 236 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 237 | [RHAddressBook setPreemptiveGeocodingEnabled:![RHAddressBook isPreemptiveGeocodingEnabled]]; 238 | #endif 239 | #endif //end Geocoding 240 | 241 | [[self.tableView cellForRowAtIndexPath:indexPath] setSelected:NO]; 242 | [self.tableView reloadData]; 243 | 244 | } else if (indexPath.section == kRHAddressBookViewControllerGroupsSection) { //fall through to creation 245 | [self addNewGroup]; 246 | } else if (indexPath.section == kRHAddressBookViewControllerPeopleSection) { 247 | [self addNewPerson]; 248 | } 249 | 250 | if (pushController){ 251 | [self.navigationController pushViewController:pushController animated:YES]; 252 | [pushController release]; 253 | } 254 | 255 | } 256 | 257 | #pragma mark - cell config 258 | 259 | -(void)configureCell:(UITableViewCell*)cell forInfoAtRow:(NSInteger)row{ 260 | cell.textLabel.text = nil; 261 | cell.accessoryType = UITableViewCellAccessoryNone; 262 | cell.selectionStyle = UITableViewCellSelectionStyleNone; 263 | switch (row) { 264 | 265 | case 0: cell.textLabel.text = [NSString stringWithFormat:@"sortOrdering = %i", [RHAddressBook sortOrdering]]; break; 266 | case 1: cell.textLabel.text = [NSString stringWithFormat:@"compositeNameFormat = %i", [RHAddressBook compositeNameFormat]]; break; 267 | default: cell.textLabel.text = NSLocalizedString(@"-", nil); 268 | } 269 | 270 | } 271 | 272 | -(void)configureCell:(UITableViewCell*)cell forLocationAtRow:(NSInteger)row{ 273 | cell.textLabel.text = nil; 274 | cell.accessoryType = UITableViewCellAccessoryNone; 275 | cell.selectionStyle = UITableViewCellSelectionStyleNone; 276 | 277 | switch (row) { 278 | 279 | #if RH_AB_INCLUDE_GEOCODING 280 | case 0: cell.textLabel.text = [NSString stringWithFormat:@"GeocodingSupported = %i", [RHAddressBook isGeocodingSupported]]; break; 281 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 282 | case 2: cell.textLabel.text = [NSString stringWithFormat:@"GeocodingProgress = %f", [_addressBook preemptiveGeocodingProgress]]; break; 283 | case 1: cell.textLabel.text = [NSString stringWithFormat:@"GeocodingEnabled = %i", [RHAddressBook isPreemptiveGeocodingEnabled]]; break; 284 | case 3: cell.textLabel.text = @"Toggle Geocoding"; cell.selectionStyle = UITableViewCellSelectionStyleBlue; break; 285 | #endif 286 | default: cell.textLabel.text = NSLocalizedString(@"-", nil); 287 | #else 288 | default: cell.textLabel.text = NSLocalizedString(@"No Geo Support", nil); 289 | #endif //end Geocoding 290 | } 291 | 292 | } 293 | 294 | -(void)configureCell:(UITableViewCell*)cell forSourceAtRow:(NSInteger)row{ 295 | RHSource *source = [_sources objectAtIndex:row]; 296 | 297 | if ([source isEqual:[_addressBook defaultSource]]){ 298 | cell.textLabel.text = NSLocalizedString(@"Default Source", nil); 299 | 300 | } else { 301 | cell.textLabel.text = source.compositeName; 302 | } 303 | cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; 304 | 305 | } 306 | -(void)configureCell:(UITableViewCell*)cell forGroupAtRow:(NSInteger)row{ 307 | if (row < [_groups count]){ 308 | RHGroup *group = [_groups objectAtIndex:row]; 309 | cell.textLabel.text = [NSString stringWithFormat:NSLocalizedString(@"%@ - %lu Members",nil), group.compositeName, (unsigned long)[[group members] count]]; 310 | cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; 311 | 312 | } else { 313 | //assume adding a new row 314 | cell.textLabel.text = NSLocalizedString(@"Add New Group...", nil); 315 | cell.accessoryType = UITableViewCellAccessoryNone; 316 | } 317 | 318 | } 319 | -(void)configureCell:(UITableViewCell*)cell forPersonAtRow:(NSInteger)row{ 320 | if (row < [_people count]){ 321 | RHPerson *person = [_people objectAtIndex:row]; 322 | cell.textLabel.text = person.compositeName; 323 | cell.imageView.image = person.thumbnail; 324 | cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; 325 | 326 | } else { 327 | //assume adding a new row 328 | cell.textLabel.text = NSLocalizedString(@"Add New Person...", nil); 329 | cell.accessoryType = UITableViewCellAccessoryNone; 330 | } 331 | } 332 | 333 | #pragma mark - add new objects 334 | -(void)addNewGroup{ 335 | RHGroup *group = [_addressBook newGroupInDefaultSource]; 336 | group.name = NSLocalizedString(@"New Group", nil); 337 | [_addressBook save]; 338 | [_groups addObject:group]; 339 | [group release]; 340 | } 341 | 342 | -(void)addNewPerson{ 343 | RHPerson *person = [_addressBook newPersonInDefaultSource]; 344 | person.firstName = NSLocalizedString(@"New Person", nil); 345 | [_addressBook save]; 346 | [_people addObject:person]; 347 | [person release]; 348 | } 349 | 350 | #pragma mark - addressBookChangedNotification 351 | -(void)addressBookChanged:(NSNotification*)notification{ 352 | [_addressBook revert]; //so we pick up the remove changes 353 | [self.tableView reloadData]; 354 | } 355 | 356 | @end 357 | 358 | 359 | -------------------------------------------------------------------------------- /RHAddressBook/RHAddressBookSharedServices.m: -------------------------------------------------------------------------------- 1 | // 2 | // RHAddressBookSharedServices.m 3 | // RHAddressBook 4 | // 5 | // Created by Richard Heard on 11/11/11. 6 | // Copyright (c) 2011 Richard Heard. All rights reserved. 7 | // 8 | // Redistribution and use in source and binary forms, with or without 9 | // modification, are permitted provided that the following conditions 10 | // are met: 11 | // 1. Redistributions of source code must retain the above copyright 12 | // notice, this list of conditions and the following disclaimer. 13 | // 2. Redistributions in binary form must reproduce the above copyright 14 | // notice, this list of conditions and the following disclaimer in the 15 | // documentation and/or other materials provided with the distribution. 16 | // 3. The name of the author may not be used to endorse or promote products 17 | // derived from this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | // 30 | 31 | #import "RHAddressBookSharedServices.h" 32 | 33 | #if RH_AB_INCLUDE_GEOCODING 34 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 35 | #import "RHAddressBookGeoResult.h" 36 | #endif //end iOS5+ 37 | #endif //end Geocoding 38 | 39 | #import "NSThread+RHBlockAdditions.h" 40 | #import "RHAddressBook.h" 41 | #import "RHAddressBookThreadMain.h" 42 | 43 | #import 44 | #import 45 | 46 | #define PROCESS_ADDRESS_EVERY_SECONDS 5.0 //seconds between each geocode 47 | 48 | //private 49 | @interface RHAddressBookSharedServices () 50 | 51 | #if RH_AB_INCLUDE_GEOCODING 52 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 53 | 54 | //cache 55 | -(void)loadCache; 56 | -(void)writeCache; 57 | -(void)purgeCache; 58 | -(void)rebuildCache; 59 | -(NSString*)cacheFilePath; 60 | 61 | //geocoding 62 | -(RHAddressBookGeoResult*)cacheEntryForPersonID:(ABRecordID)pid addressID:(ABPropertyID)aid; 63 | -(void)processAddressesMissingLocationInfo; 64 | -(void)processTimerFire; 65 | 66 | #endif //end iOS5+ 67 | #endif //end Geocoding 68 | 69 | 70 | //addressbook notifications 71 | -(void)registerForAddressBookChanges; 72 | -(void)deregisterForAddressBookChanges; 73 | void RHAddressBookExternalChangeCallback (ABAddressBookRef addressBook, CFDictionaryRef info, void *context ); 74 | 75 | 76 | @end 77 | 78 | @implementation RHAddressBookSharedServices { 79 | //we have our own instance of the address book 80 | ABAddressBookRef _addressBook; 81 | NSThread *_addressBookThread; //perform all address book operations on this thread. (AB is not thread safe. :() 82 | 83 | #if RH_AB_INCLUDE_GEOCODING 84 | NSMutableArray *_cache; //array of RHAddressBookGeoResult objects 85 | NSTimer *_timer; 86 | #endif //end Geocoding 87 | 88 | } 89 | 90 | #pragma mark - singleton 91 | static __strong RHAddressBookSharedServices *_sharedInstance = nil; 92 | 93 | +(id)sharedInstance{ 94 | if (_sharedInstance) return _sharedInstance; //for performance reasons, check outside @synchronized 95 | 96 | @synchronized([self class]){ 97 | if (!_sharedInstance){ 98 | _sharedInstance = [[super allocWithZone:NULL] init]; 99 | } 100 | } 101 | 102 | return _sharedInstance; 103 | } 104 | 105 | +(id)allocWithZone:(NSZone *)zone{ 106 | return arc_retain([self sharedInstance]); 107 | } 108 | 109 | -(instancetype)init { 110 | 111 | self = [super init]; 112 | if (self) { 113 | 114 | //because NSThread retains its target, we use a placeholder object that contains the threads main method 115 | RHAddressBookThreadMain *threadMain = arc_autorelease([[RHAddressBookThreadMain alloc] init]); 116 | _addressBookThread = [[NSThread alloc] initWithTarget:threadMain selector:@selector(threadMain:) object:nil]; 117 | [_addressBookThread setName:[NSString stringWithFormat:@"RHAddressBookSharedServicesThread for %p", self]]; 118 | [_addressBookThread start]; 119 | 120 | 121 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 60000 122 | if (ABAddressBookCreateWithOptions != NULL){ 123 | __block CFErrorRef errorRef = NULL; 124 | [_addressBookThread rh_performBlock:^{ 125 | _addressBook = ABAddressBookCreateWithOptions(nil, &errorRef); 126 | }]; 127 | 128 | if (!_addressBook){ 129 | //bail 130 | RHErrorLog(@"Error: Failed to create RHAddressBookSharedServices instance. Underlying ABAddressBookCreateWithOptions() failed with error: %@", errorRef); 131 | if (errorRef) CFRelease(errorRef); 132 | 133 | arc_release_nil(self); 134 | 135 | return nil; 136 | } 137 | 138 | } else { 139 | #endif //end iOS6+ 140 | 141 | #pragma clang diagnostic push 142 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 143 | [_addressBookThread rh_performBlock:^{ 144 | _addressBook = ABAddressBookCreate(); 145 | }]; 146 | #pragma clang diagnostic pop 147 | 148 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 60000 149 | } 150 | #endif //end iOS6+ 151 | 152 | 153 | #if RH_AB_INCLUDE_GEOCODING 154 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 155 | if ([RHAddressBookSharedServices isGeocodingSupported]){ 156 | [self loadCache]; 157 | [self rebuildCache]; 158 | } 159 | #endif //end iOS5+ 160 | #endif //end Geocoding 161 | 162 | [self registerForAddressBookChanges]; 163 | 164 | } 165 | return self; 166 | } 167 | 168 | -(id)copyWithZone:(NSZone *)zone{ 169 | return self; 170 | } 171 | 172 | #pragma mark - cleanup 173 | -(void)dealloc { 174 | //do stuff (even though we are a singleton) 175 | [self deregisterForAddressBookChanges]; 176 | 177 | if (_addressBook) { CFRelease(_addressBook); _addressBook = NULL; } 178 | 179 | [_addressBookThread cancel]; 180 | arc_release_nil(_addressBookThread); 181 | 182 | #if RH_AB_INCLUDE_GEOCODING 183 | arc_release_nil(_cache); 184 | arc_release_nil(_timer); 185 | #endif //end Geocoding 186 | 187 | arc_super_dealloc(); 188 | } 189 | 190 | #if RH_AB_INCLUDE_GEOCODING 191 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 192 | 193 | #pragma mark - cache management 194 | -(void)loadCache{ 195 | RHLog(@""); 196 | arc_release(_cache); 197 | _cache = arc_retain([NSKeyedUnarchiver unarchiveObjectWithFile:[self cacheFilePath]]); 198 | 199 | //if unarchive failed or on first run 200 | if (!_cache) _cache = [[NSMutableArray alloc] init]; 201 | 202 | } 203 | 204 | -(void)writeCache{ 205 | RHLog(@""); 206 | [NSKeyedArchiver archiveRootObject:_cache toFile:[self cacheFilePath]]; 207 | 208 | } 209 | 210 | -(void)purgeCache{ 211 | RHLog(@""); 212 | [[NSFileManager defaultManager] removeItemAtPath:[self cacheFilePath] error:nil]; 213 | [self loadCache]; 214 | } 215 | 216 | //creates a new cache array, pulling over all existing values from the old cache array that are useable 217 | -(void)rebuildCache{ 218 | if (![[NSThread currentThread] isEqual:_addressBookThread]){ 219 | [self performSelector:_cmd onThread:_addressBookThread withObject:nil waitUntilDone:YES]; 220 | return; 221 | } 222 | RHLog(@""); 223 | 224 | NSMutableArray *newCache = [NSMutableArray array]; 225 | 226 | //make sure the address book instance is up to date 227 | ABAddressBookRevert(_addressBook); 228 | 229 | CFArrayRef people = ABAddressBookCopyArrayOfAllPeople(_addressBook); 230 | 231 | if (people){ 232 | for (CFIndex i = 0; i < CFArrayGetCount(people); i++) { 233 | 234 | ABRecordRef person = CFArrayGetValueAtIndex(people, i); 235 | 236 | if (person){ 237 | 238 | ABRecordID personID = ABRecordGetRecordID(person); 239 | ABMultiValueRef addresses = ABRecordCopyValue(person, kABPersonAddressProperty); 240 | 241 | if (addresses){ 242 | for (CFIndex i = 0; i < ABMultiValueGetCount(addresses); i++) { 243 | 244 | ABPropertyID addressID = ABMultiValueGetIdentifierAtIndex(addresses, i); 245 | CFDictionaryRef addressDict = ABMultiValueCopyValueAtIndex(addresses, i); 246 | //====================================================================== 247 | 248 | //see if we have a valid, old entry 249 | RHAddressBookGeoResult* old = [self cacheEntryForPersonID:personID addressID:addressID]; 250 | 251 | if (old && [old isValid]){ 252 | //yes 253 | [newCache addObject:old]; // just add it and be done. 254 | } else { 255 | // not valid, create a new entry 256 | RHAddressBookGeoResult* new = [[RHAddressBookGeoResult alloc] initWithPersonID:personID addressID:addressID]; 257 | [newCache addObject:new]; 258 | arc_release(new); 259 | } 260 | 261 | //====================================================================== 262 | if (addressDict) CFRelease(addressDict); 263 | } 264 | 265 | CFRelease(addresses); 266 | } //addresses 267 | } //person 268 | } 269 | 270 | CFRelease(people); 271 | } //people 272 | 273 | //swap old cache with the new 274 | arc_release(_cache); 275 | _cache = arc_retain(newCache); 276 | 277 | [self processAddressesMissingLocationInfo]; 278 | [self writeCache]; //get it to disk asap 279 | 280 | } 281 | 282 | 283 | -(RHAddressBookGeoResult*)cacheEntryForPersonID:(ABRecordID)pid addressID:(ABPropertyID)aid{ 284 | for (RHAddressBookGeoResult *entry in _cache) { 285 | if (entry.personID == pid && entry.addressID == aid){ 286 | return arc_autorelease(arc_retain(entry)); 287 | } 288 | } 289 | 290 | return nil; 291 | } 292 | 293 | -(NSString*)cacheFilePath{ 294 | 295 | //cache 296 | NSString *path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; 297 | 298 | NSString *applicationID = [[[NSBundle mainBundle] infoDictionary] valueForKey:(NSString *)kCFBundleIdentifierKey]; 299 | path = [path stringByAppendingPathComponent:applicationID]; 300 | 301 | path = [path stringByAppendingPathComponent:@"RHAddressBookGeoCache.cache"]; 302 | 303 | return path; 304 | } 305 | 306 | 307 | #pragma mark - Geocoding Process 308 | -(void)processAddressesMissingLocationInfo{ 309 | 310 | //don't do any geocoding if its not available (iOS 5+ only) 311 | if (![RHAddressBookSharedServices isGeocodingSupported]) return; 312 | 313 | //if disabled, do nothing 314 | if (![self.class isPreemptiveGeocodingEnabled]) return; 315 | 316 | 317 | if (!_timer){ 318 | _timer = arc_retain([NSTimer scheduledTimerWithTimeInterval:PROCESS_ADDRESS_EVERY_SECONDS target:self selector:@selector(processTimerFire) userInfo:nil repeats:YES]); 319 | } 320 | } 321 | 322 | -(void)processTimerFire{ 323 | 324 | //if we are offline, the geocode fails with a specific error 325 | // in that instance we don't set the resultNotFound flag, so next time around we will re-attempt the particular address. 326 | //TODO: we really should handle this better, with our shared services class observing some form of reachability and pausing / resuming the timer. 327 | 328 | //write the cache periodically, not just at the end... incase we... you know..... yea..... 329 | [self writeCache]; 330 | 331 | //if we have been disabled, stop working 332 | if (![self.class isPreemptiveGeocodingEnabled]){ 333 | [_timer invalidate]; 334 | arc_release_nil(_timer); 335 | RHLog(@"Location Lookup has been disabled."); 336 | return; 337 | } 338 | 339 | //look for next unprocessed entry 340 | for (RHAddressBookGeoResult *entry in _cache) { 341 | if (!entry.location && !entry.resultNotFound){ 342 | //needs processing 343 | [entry geocodeAssociatedAddressDictionary]; //if this is called and the entry is already geocoding, its just a no-op and so is an easy way for us to bail 344 | return; 345 | } 346 | } 347 | 348 | //we are done, all addresses processed 349 | [self writeCache]; 350 | [_timer invalidate]; 351 | arc_release_nil(_timer); 352 | 353 | RHLog(@"Location Lookup Processing done."); 354 | 355 | } 356 | 357 | #pragma mark - Geocode Lookup 358 | //forward 359 | -(CLPlacemark*)placemarkForPersonID:(ABRecordID)personID addressID:(ABMultiValueIdentifier)addressID{ 360 | RHAddressBookGeoResult *cacheEntry = [self cacheEntryForPersonID:personID addressID:addressID]; 361 | if (cacheEntry && !cacheEntry.placemark && !cacheEntry.resultNotFound && [self.class isGeocodingSupported] && !_timer) { 362 | //lets force a geocode for this one address 363 | [cacheEntry geocodeAssociatedAddressDictionary]; 364 | } 365 | return [cacheEntry placemark]; 366 | } 367 | 368 | -(CLLocation*)locationForPersonID:(ABRecordID)personID addressID:(ABMultiValueIdentifier)addressID{ 369 | return [[self placemarkForPersonID:personID addressID:addressID] location]; 370 | } 371 | 372 | //reverse 373 | -(NSArray*)geoResultsWithinDistance:(CLLocationDistance)distance ofLocation:(CLLocation*)location{ 374 | NSMutableArray *results = [[NSMutableArray alloc] init]; 375 | 376 | for (RHAddressBookGeoResult *entry in _cache) { 377 | if (entry.location) { 378 | CLLocationDistance tmpDistance = [entry.location distanceFromLocation:location]; 379 | if (tmpDistance < distance) { 380 | //within radius 381 | [results addObject:entry]; 382 | } 383 | } 384 | } 385 | 386 | return arc_autorelease(results); 387 | } 388 | 389 | -(RHAddressBookGeoResult*)geoResultClosestToLocation:(CLLocation*)location{ 390 | return [self geoResultClosestToLocation:location distanceOut:nil]; 391 | } 392 | 393 | -(RHAddressBookGeoResult*)geoResultClosestToLocation:(CLLocation*)location distanceOut:(CLLocationDistance*)distanceOut{ 394 | 395 | CLLocationDistance distance = DBL_MAX; 396 | RHAddressBookGeoResult *result = nil; 397 | 398 | for (RHAddressBookGeoResult *entry in _cache) { 399 | if (entry.location) { 400 | CLLocationDistance tmpDistance = [entry.location distanceFromLocation:location]; 401 | if (tmpDistance < distance) { 402 | //closer point 403 | result = entry; 404 | distance = tmpDistance; 405 | } 406 | } 407 | } 408 | 409 | if (distanceOut) *distanceOut = distance; 410 | return result; 411 | } 412 | 413 | #endif //end iOS5+ 414 | 415 | #pragma mark - geocoding settings 416 | NSString static * RHAddressBookSharedServicesPreemptiveGeocodingEnabled = @"RHAddressBookSharedServicesPreemptiveGeocodingEnabled"; 417 | 418 | +(BOOL)isPreemptiveGeocodingEnabled{ 419 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 420 | if ([RHAddressBookSharedServices isGeocodingSupported]){ 421 | return [[NSUserDefaults standardUserDefaults] boolForKey:RHAddressBookSharedServicesPreemptiveGeocodingEnabled]; 422 | } 423 | #endif //end iOS5+ 424 | return NO; 425 | } 426 | 427 | +(void)setPreemptiveGeocodingEnabled:(BOOL)enabled{ 428 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 429 | if ([RHAddressBookSharedServices isGeocodingSupported]){ 430 | [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:RHAddressBookSharedServicesPreemptiveGeocodingEnabled]; 431 | //for the disabled->enabled case 432 | if (_sharedInstance)[_sharedInstance processAddressesMissingLocationInfo]; 433 | } 434 | #endif //end iOS5+ 435 | 436 | } 437 | 438 | -(float)preemptiveGeocodingProgress{ 439 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 440 | if ([RHAddressBookSharedServices isGeocodingSupported]){ 441 | NSInteger incomplete = 0; 442 | for (RHAddressBookGeoResult *entry in _cache) { 443 | if (!entry.location && !entry.resultNotFound){ 444 | incomplete++; 445 | } 446 | } 447 | 448 | if ([_cache count] == 0) return 1.0f; 449 | 450 | return 1.0f - ((float)incomplete / (float)[_cache count]); 451 | } 452 | #endif //end iOS5+ 453 | 454 | return 0.0f; 455 | } 456 | 457 | 458 | +(BOOL)isGeocodingSupported{ 459 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 460 | //the response to selector check is required because iOS4 actually has a private CLGeocoder class. 461 | return ([CLGeocoder class] && [CLGeocoder instancesRespondToSelector:@selector(geocodeAddressDictionary:completionHandler:)]); 462 | #endif //end iOS5+ 463 | return NO; //if not compiled with Geocoding, return false, always 464 | } 465 | 466 | #endif //end Geocoding 467 | 468 | 469 | #pragma mark - addressbook changes 470 | 471 | -(void)registerForAddressBookChanges{ 472 | if (![[NSThread currentThread] isEqual:_addressBookThread]){ 473 | [self performSelector:_cmd onThread:_addressBookThread withObject:nil waitUntilDone:YES]; 474 | return; 475 | } 476 | 477 | ABAddressBookRegisterExternalChangeCallback(_addressBook, RHAddressBookExternalChangeCallback, (__bridge void *)(self)); //use the context as a pointer to self 478 | 479 | } 480 | 481 | -(void)deregisterForAddressBookChanges{ 482 | if (![[NSThread currentThread] isEqual:_addressBookThread]){ 483 | [self performSelector:_cmd onThread:_addressBookThread withObject:nil waitUntilDone:YES]; 484 | return; 485 | } 486 | 487 | // when unregistering a callback both the callback and the context 488 | // need to match the ones that were registered. 489 | if (_addressBook){ 490 | ABAddressBookUnregisterExternalChangeCallback(_addressBook, RHAddressBookExternalChangeCallback, (__bridge void *)(self)); 491 | } 492 | 493 | } 494 | 495 | void RHAddressBookExternalChangeCallback (ABAddressBookRef addressBook, CFDictionaryRef info, void *context ){ 496 | 497 | #if RH_AB_INCLUDE_GEOCODING 498 | RHLog(@"AddressBook changed externally. Rebuilding RHABGeoCache"); 499 | 500 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 501 | if ([RHAddressBookSharedServices isGeocodingSupported]){ 502 | [(__bridge RHAddressBookSharedServices*)context rebuildCache]; //use the context as a pointer to self 503 | } 504 | #endif //end iOS5+ 505 | #endif //end Geocoding 506 | 507 | //post external change notification for public clients, on the main thread 508 | dispatch_async(dispatch_get_main_queue(), ^{ 509 | [[NSNotificationCenter defaultCenter] postNotificationName:RHAddressBookExternalChangeNotification object:nil]; 510 | }); 511 | } 512 | 513 | 514 | 515 | 516 | @end 517 | --------------------------------------------------------------------------------