Repository: mwaterfall/MWPhotoBrowser Branch: master Commit: 66ddd7385997 Files: 282 Total size: 1.1 MB Directory structure: gitextract_fy6u_tc2/ ├── .gitignore ├── .travis.yml ├── Assets/ │ ├── ImageError.psd │ ├── ImageError@2x.psd │ ├── ImageError@3x.psd │ ├── ImageSelected.psd │ ├── ImageSelected@2x.psd │ ├── ImageSelected@3x.psd │ ├── ImageSelectedSmallOff.psd │ ├── ImageSelectedSmallOff@2x.psd │ ├── ImageSelectedSmallOff@3x.psd │ ├── ImageSelectedSmallOn.psd │ ├── ImageSelectedSmallOn@2x.psd │ ├── ImageSelectedSmallOn@3x.psd │ ├── PlayButtonOverlayLarge.psd │ ├── PlayButtonOverlayLarge@2x.psd │ ├── PlayButtonOverlayLarge@3x.psd │ ├── PlayButtonOverlayLargeTap.psd │ ├── PlayButtonOverlayLargeTap@2x.psd │ ├── PlayButtonOverlayLargeTap@3x.psd │ ├── UIBarButtonItemArrow.psd │ ├── UIBarButtonItemArrow@2x.psd │ ├── UIBarButtonItemArrow@3x.psd │ ├── UIBarButtonItemGrid.psd │ ├── UIBarButtonItemGrid@2x.psd │ ├── UIBarButtonItemGrid@3x.psd │ ├── VideoOverlay.psd │ ├── VideoOverlay@2x.psd │ └── VideoOverlay@3x.psd ├── Example/ │ ├── MWPhotoBrowser/ │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Images.xcassets/ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ ├── ImageSelected.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── ImageSelectedSmall.imageset/ │ │ │ │ └── Contents.json │ │ │ └── LaunchImage.launchimage/ │ │ │ └── Contents.json │ │ ├── MWPhotoBrowser-Info.plist │ │ ├── MWPhotoBrowser-Prefix.pch │ │ ├── Main.storyboard │ │ ├── Menu.h │ │ ├── Menu.m │ │ ├── en.lproj/ │ │ │ └── InfoPlist.strings │ │ └── main.m │ ├── MWPhotoBrowser.xcodeproj/ │ │ ├── project.pbxproj │ │ ├── project.xcworkspace/ │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── xcschemes/ │ │ └── MWPhotoBrowser-Example.xcscheme │ ├── MWPhotoBrowser.xcworkspace/ │ │ └── contents.xcworkspacedata │ ├── Podfile │ ├── Pods/ │ │ ├── DACircularProgress/ │ │ │ ├── DACircularProgress/ │ │ │ │ ├── DACircularProgressView.h │ │ │ │ ├── DACircularProgressView.m │ │ │ │ ├── DALabeledCircularProgressView.h │ │ │ │ └── DALabeledCircularProgressView.m │ │ │ ├── LICENSE.md │ │ │ └── README.md │ │ ├── Expecta/ │ │ │ ├── Expecta/ │ │ │ │ ├── EXPBlockDefinedMatcher.h │ │ │ │ ├── EXPBlockDefinedMatcher.m │ │ │ │ ├── EXPDefines.h │ │ │ │ ├── EXPDoubleTuple.h │ │ │ │ ├── EXPDoubleTuple.m │ │ │ │ ├── EXPExpect.h │ │ │ │ ├── EXPExpect.m │ │ │ │ ├── EXPFloatTuple.h │ │ │ │ ├── EXPFloatTuple.m │ │ │ │ ├── EXPMatcher.h │ │ │ │ ├── EXPUnsupportedObject.h │ │ │ │ ├── EXPUnsupportedObject.m │ │ │ │ ├── Expecta.h │ │ │ │ ├── ExpectaObject.h │ │ │ │ ├── ExpectaObject.m │ │ │ │ ├── ExpectaSupport.h │ │ │ │ ├── ExpectaSupport.m │ │ │ │ ├── Matchers/ │ │ │ │ │ ├── EXPMatcherHelpers.h │ │ │ │ │ ├── EXPMatcherHelpers.m │ │ │ │ │ ├── EXPMatchers+beCloseTo.h │ │ │ │ │ ├── EXPMatchers+beCloseTo.m │ │ │ │ │ ├── EXPMatchers+beFalsy.h │ │ │ │ │ ├── EXPMatchers+beFalsy.m │ │ │ │ │ ├── EXPMatchers+beGreaterThan.h │ │ │ │ │ ├── EXPMatchers+beGreaterThan.m │ │ │ │ │ ├── EXPMatchers+beGreaterThanOrEqualTo.h │ │ │ │ │ ├── EXPMatchers+beGreaterThanOrEqualTo.m │ │ │ │ │ ├── EXPMatchers+beIdenticalTo.h │ │ │ │ │ ├── EXPMatchers+beIdenticalTo.m │ │ │ │ │ ├── EXPMatchers+beInTheRangeOf.h │ │ │ │ │ ├── EXPMatchers+beInTheRangeOf.m │ │ │ │ │ ├── EXPMatchers+beInstanceOf.h │ │ │ │ │ ├── EXPMatchers+beInstanceOf.m │ │ │ │ │ ├── EXPMatchers+beKindOf.h │ │ │ │ │ ├── EXPMatchers+beKindOf.m │ │ │ │ │ ├── EXPMatchers+beLessThan.h │ │ │ │ │ ├── EXPMatchers+beLessThan.m │ │ │ │ │ ├── EXPMatchers+beLessThanOrEqualTo.h │ │ │ │ │ ├── EXPMatchers+beLessThanOrEqualTo.m │ │ │ │ │ ├── EXPMatchers+beNil.h │ │ │ │ │ ├── EXPMatchers+beNil.m │ │ │ │ │ ├── EXPMatchers+beSubclassOf.h │ │ │ │ │ ├── EXPMatchers+beSubclassOf.m │ │ │ │ │ ├── EXPMatchers+beSupersetOf.h │ │ │ │ │ ├── EXPMatchers+beSupersetOf.m │ │ │ │ │ ├── EXPMatchers+beTruthy.h │ │ │ │ │ ├── EXPMatchers+beTruthy.m │ │ │ │ │ ├── EXPMatchers+beginWith.h │ │ │ │ │ ├── EXPMatchers+beginWith.m │ │ │ │ │ ├── EXPMatchers+conformTo.h │ │ │ │ │ ├── EXPMatchers+conformTo.m │ │ │ │ │ ├── EXPMatchers+contain.h │ │ │ │ │ ├── EXPMatchers+contain.m │ │ │ │ │ ├── EXPMatchers+endWith.h │ │ │ │ │ ├── EXPMatchers+endWith.m │ │ │ │ │ ├── EXPMatchers+equal.h │ │ │ │ │ ├── EXPMatchers+equal.m │ │ │ │ │ ├── EXPMatchers+haveCountOf.h │ │ │ │ │ ├── EXPMatchers+haveCountOf.m │ │ │ │ │ ├── EXPMatchers+match.h │ │ │ │ │ ├── EXPMatchers+match.m │ │ │ │ │ ├── EXPMatchers+postNotification.h │ │ │ │ │ ├── EXPMatchers+postNotification.m │ │ │ │ │ ├── EXPMatchers+raise.h │ │ │ │ │ ├── EXPMatchers+raise.m │ │ │ │ │ ├── EXPMatchers+raiseWithReason.h │ │ │ │ │ ├── EXPMatchers+raiseWithReason.m │ │ │ │ │ ├── EXPMatchers+respondTo.h │ │ │ │ │ ├── EXPMatchers+respondTo.m │ │ │ │ │ └── EXPMatchers.h │ │ │ │ ├── NSObject+Expecta.h │ │ │ │ ├── NSValue+Expecta.h │ │ │ │ └── NSValue+Expecta.m │ │ │ ├── LICENSE │ │ │ └── README.md │ │ ├── Expecta+Snapshots/ │ │ │ ├── EXPMatchers+FBSnapshotTest.h │ │ │ ├── EXPMatchers+FBSnapshotTest.m │ │ │ ├── LICENSE.md │ │ │ └── README.md │ │ ├── FBSnapshotTestCase/ │ │ │ ├── FBSnapshotTestCase/ │ │ │ │ ├── FBSnapshotTestCase.h │ │ │ │ ├── FBSnapshotTestCase.m │ │ │ │ ├── FBSnapshotTestCasePlatform.h │ │ │ │ ├── FBSnapshotTestCasePlatform.m │ │ │ │ ├── FBSnapshotTestController.h │ │ │ │ ├── FBSnapshotTestController.m │ │ │ │ ├── UIImage+Compare.h │ │ │ │ ├── UIImage+Compare.m │ │ │ │ ├── UIImage+Diff.h │ │ │ │ └── UIImage+Diff.m │ │ │ ├── LICENSE │ │ │ └── README.md │ │ ├── Local Podspecs/ │ │ │ └── MWPhotoBrowser.podspec.json │ │ ├── MBProgressHUD/ │ │ │ ├── LICENSE │ │ │ ├── MBProgressHUD.h │ │ │ ├── MBProgressHUD.m │ │ │ └── README.mdown │ │ ├── Pods.xcodeproj/ │ │ │ ├── project.pbxproj │ │ │ ├── project.xcworkspace/ │ │ │ │ └── contents.xcworkspacedata │ │ │ └── xcshareddata/ │ │ │ └── xcschemes/ │ │ │ ├── MWPhotoBrowser.xcscheme │ │ │ ├── Pods-MWPhotoBrowser_Example-MWPhotoBrowser-MWPhotoBrowser.xcscheme │ │ │ ├── Pods-MWPhotoBrowser_Example-MWPhotoBrowser.xcscheme │ │ │ ├── Pods-MWPhotoBrowser_Tests-MWPhotoBrowser-MWPhotoBrowser.xcscheme │ │ │ └── Pods-MWPhotoBrowser_Tests-MWPhotoBrowser.xcscheme │ │ ├── SDWebImage/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ └── SDWebImage/ │ │ │ ├── NSData+ImageContentType.h │ │ │ ├── NSData+ImageContentType.m │ │ │ ├── SDImageCache.h │ │ │ ├── SDImageCache.m │ │ │ ├── SDWebImageCompat.h │ │ │ ├── SDWebImageCompat.m │ │ │ ├── SDWebImageDecoder.h │ │ │ ├── SDWebImageDecoder.m │ │ │ ├── SDWebImageDownloader.h │ │ │ ├── SDWebImageDownloader.m │ │ │ ├── SDWebImageDownloaderOperation.h │ │ │ ├── SDWebImageDownloaderOperation.m │ │ │ ├── SDWebImageManager.h │ │ │ ├── SDWebImageManager.m │ │ │ ├── SDWebImageOperation.h │ │ │ ├── SDWebImagePrefetcher.h │ │ │ ├── SDWebImagePrefetcher.m │ │ │ ├── UIButton+WebCache.h │ │ │ ├── UIButton+WebCache.m │ │ │ ├── UIImage+GIF.h │ │ │ ├── UIImage+GIF.m │ │ │ ├── UIImage+MultiFormat.h │ │ │ ├── UIImage+MultiFormat.m │ │ │ ├── UIImageView+HighlightedWebCache.h │ │ │ ├── UIImageView+HighlightedWebCache.m │ │ │ ├── UIImageView+WebCache.h │ │ │ ├── UIImageView+WebCache.m │ │ │ ├── UIView+WebCacheOperation.h │ │ │ └── UIView+WebCacheOperation.m │ │ ├── Specta/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ └── Specta/ │ │ │ └── Specta/ │ │ │ ├── SPTCallSite.h │ │ │ ├── SPTCallSite.m │ │ │ ├── SPTCompiledExample.h │ │ │ ├── SPTCompiledExample.m │ │ │ ├── SPTExample.h │ │ │ ├── SPTExample.m │ │ │ ├── SPTExampleGroup.h │ │ │ ├── SPTExampleGroup.m │ │ │ ├── SPTExcludeGlobalBeforeAfterEach.h │ │ │ ├── SPTGlobalBeforeAfterEach.h │ │ │ ├── SPTSharedExampleGroups.h │ │ │ ├── SPTSharedExampleGroups.m │ │ │ ├── SPTSpec.h │ │ │ ├── SPTSpec.m │ │ │ ├── SPTTestSuite.h │ │ │ ├── SPTTestSuite.m │ │ │ ├── Specta.h │ │ │ ├── SpectaDSL.h │ │ │ ├── SpectaDSL.m │ │ │ ├── SpectaTypes.h │ │ │ ├── SpectaUtility.h │ │ │ ├── SpectaUtility.m │ │ │ ├── XCTest+Private.h │ │ │ ├── XCTestCase+Specta.h │ │ │ └── XCTestCase+Specta.m │ │ └── Target Support Files/ │ │ ├── DACircularProgress/ │ │ │ ├── DACircularProgress-dummy.m │ │ │ ├── DACircularProgress-prefix.pch │ │ │ └── DACircularProgress.xcconfig │ │ ├── Expecta/ │ │ │ ├── Expecta-dummy.m │ │ │ ├── Expecta-prefix.pch │ │ │ └── Expecta.xcconfig │ │ ├── Expecta+Snapshots/ │ │ │ ├── Expecta+Snapshots-dummy.m │ │ │ ├── Expecta+Snapshots-prefix.pch │ │ │ └── Expecta+Snapshots.xcconfig │ │ ├── FBSnapshotTestCase/ │ │ │ ├── FBSnapshotTestCase-dummy.m │ │ │ ├── FBSnapshotTestCase-prefix.pch │ │ │ └── FBSnapshotTestCase.xcconfig │ │ ├── MBProgressHUD/ │ │ │ ├── MBProgressHUD-dummy.m │ │ │ ├── MBProgressHUD-prefix.pch │ │ │ └── MBProgressHUD.xcconfig │ │ ├── MWPhotoBrowser/ │ │ │ ├── MWPhotoBrowser-dummy.m │ │ │ ├── MWPhotoBrowser-prefix.pch │ │ │ └── MWPhotoBrowser.xcconfig │ │ ├── Pods-MWPhotoBrowser_Example/ │ │ │ ├── Pods-MWPhotoBrowser_Example-acknowledgements.markdown │ │ │ ├── Pods-MWPhotoBrowser_Example-acknowledgements.plist │ │ │ ├── Pods-MWPhotoBrowser_Example-dummy.m │ │ │ ├── Pods-MWPhotoBrowser_Example-frameworks.sh │ │ │ ├── Pods-MWPhotoBrowser_Example-resources.sh │ │ │ ├── Pods-MWPhotoBrowser_Example.debug.xcconfig │ │ │ └── Pods-MWPhotoBrowser_Example.release.xcconfig │ │ ├── Pods-MWPhotoBrowser_Tests/ │ │ │ ├── Pods-MWPhotoBrowser_Tests-acknowledgements.markdown │ │ │ ├── Pods-MWPhotoBrowser_Tests-acknowledgements.plist │ │ │ ├── Pods-MWPhotoBrowser_Tests-dummy.m │ │ │ ├── Pods-MWPhotoBrowser_Tests-frameworks.sh │ │ │ ├── Pods-MWPhotoBrowser_Tests-resources.sh │ │ │ ├── Pods-MWPhotoBrowser_Tests.debug.xcconfig │ │ │ └── Pods-MWPhotoBrowser_Tests.release.xcconfig │ │ ├── SDWebImage/ │ │ │ ├── SDWebImage-dummy.m │ │ │ ├── SDWebImage-prefix.pch │ │ │ └── SDWebImage.xcconfig │ │ └── Specta/ │ │ ├── Specta-dummy.m │ │ ├── Specta-prefix.pch │ │ └── Specta.xcconfig │ └── Tests/ │ ├── Tests-Info.plist │ ├── Tests-Prefix.pch │ ├── Tests.m │ └── en.lproj/ │ └── InfoPlist.strings ├── LICENSE ├── MWPhotoBrowser.podspec ├── Pod/ │ ├── Assets/ │ │ └── .gitkeep │ └── Classes/ │ ├── .gitkeep │ ├── MWCaptionView.h │ ├── MWCaptionView.m │ ├── MWCommon.h │ ├── MWGridCell.h │ ├── MWGridCell.m │ ├── MWGridViewController.h │ ├── MWGridViewController.m │ ├── MWPhoto.h │ ├── MWPhoto.m │ ├── MWPhotoBrowser.h │ ├── MWPhotoBrowser.m │ ├── MWPhotoBrowserPrivate.h │ ├── MWPhotoProtocol.h │ ├── MWTapDetectingImageView.h │ ├── MWTapDetectingImageView.m │ ├── MWTapDetectingView.h │ ├── MWTapDetectingView.m │ ├── MWZoomingScrollView.h │ ├── MWZoomingScrollView.m │ ├── UIImage+MWPhotoBrowser.h │ └── UIImage+MWPhotoBrowser.m └── README.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # OS X .DS_Store # Xcode build/ *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata *.xccheckout profile *.moved-aside DerivedData *.hmap *.ipa # Bundler .bundle Carthage # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control # # Note: if you ignore the Pods directory, make sure to uncomment # `pod install` in .travis.yml # # Pods/ ================================================ FILE: .travis.yml ================================================ # references: # * http://www.objc.io/issue-6/travis-ci.html # * https://github.com/supermarin/xcpretty#usage language: objective-c # cache: cocoapods # podfile: Example/Podfile # before_install: # - gem install cocoapods # Since Travis is not always on latest version # - pod install --project-directory=Example install: - gem install xcpretty --no-rdoc --no-ri --no-document --quiet script: - set -o pipefail && xcodebuild test -workspace Example/MWPhotoBrowser.xcworkspace -scheme MWPhotoBrowser-Example -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO | xcpretty -c - pod lib lint --quick ================================================ FILE: Example/MWPhotoBrowser/AppDelegate.h ================================================ // // AppDelegate.h // PhotoBrowserDemo // // Created by Michael Waterfall on 31/12/2011. // Copyright (c) 2011 __MyCompanyName__. All rights reserved. // #import @interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; //@property (strong, nonatomic) UIViewController *viewController; @end ================================================ FILE: Example/MWPhotoBrowser/AppDelegate.m ================================================ // // AppDelegate.m // PhotoBrowserDemo // // Created by Michael Waterfall on 31/12/2011. // Copyright (c) 2011 __MyCompanyName__. All rights reserved. // #import "AppDelegate.h" //#import "Menu.h" @implementation AppDelegate @synthesize window = _window; //@synthesize viewController = _viewController; - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; // // Menu *menu = [[Menu alloc] initWithStyle:UITableViewStylePlain]; // self.viewController = (UIViewController *)[[UINavigationController alloc] initWithRootViewController:menu]; // self.window.rootViewController = self.viewController; // [self.window makeKeyAndVisible]; return YES; } - (void)applicationWillResignActive:(UIApplication *)application { /* Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. */ } - (void)applicationDidEnterBackground:(UIApplication *)application { /* Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. */ } - (void)applicationWillEnterForeground:(UIApplication *)application { /* Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. */ } - (void)applicationDidBecomeActive:(UIApplication *)application { /* Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. */ } - (void)applicationWillTerminate:(UIApplication *)application { /* Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. */ } @end ================================================ FILE: Example/MWPhotoBrowser/Images.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "size" : "29x29", "scale" : "2x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "3x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "2x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "3x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "2x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "3x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "1x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "2x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "1x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "2x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "1x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "2x" }, { "idiom" : "ipad", "size" : "83.5x83.5", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Example/MWPhotoBrowser/Images.xcassets/ImageSelected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "ImageSelectedOn.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "ImageSelectedOn@2x.png" }, { "idiom" : "universal", "scale" : "3x", "filename" : "ImageSelectedOn@3x.png" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Example/MWPhotoBrowser/Images.xcassets/ImageSelectedSmall.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "ImageSelectedSmallOn.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "ImageSelectedSmallOn@2x.png" }, { "idiom" : "universal", "scale" : "3x", "filename" : "ImageSelectedSmallOn@3x.png" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Example/MWPhotoBrowser/Images.xcassets/LaunchImage.launchimage/Contents.json ================================================ { "images" : [ { "orientation" : "portrait", "idiom" : "iphone", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" }, { "orientation" : "portrait", "idiom" : "iphone", "subtype" : "retina4", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" }, { "orientation" : "portrait", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "1x" }, { "orientation" : "landscape", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "1x" }, { "orientation" : "portrait", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" }, { "orientation" : "landscape", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Example/MWPhotoBrowser/MWPhotoBrowser-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleDisplayName ${PRODUCT_NAME} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 LSRequiresIPhoneOS UILaunchStoryboardName Main UIMainStoryboardFile Main UIRequiredDeviceCapabilities armv7 UIStatusBarHidden UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance ================================================ FILE: Example/MWPhotoBrowser/MWPhotoBrowser-Prefix.pch ================================================ // // Prefix header // // The contents of this file are implicitly included at the beginning of every source file. // #import #ifndef __IPHONE_5_0 #warning "This project uses features only available in iOS SDK 5.0 and later." #endif #ifdef __OBJC__ @import UIKit; @import Foundation; #endif ================================================ FILE: Example/MWPhotoBrowser/Main.storyboard ================================================ ================================================ FILE: Example/MWPhotoBrowser/Menu.h ================================================ // // Menu.h // MWPhotoBrowser // // Created by Michael Waterfall on 21/10/2010. // Copyright 2010 d3i. All rights reserved. // #import #import "MWPhotoBrowser.h" #import @interface Menu : UITableViewController { UISegmentedControl *_segmentedControl; NSMutableArray *_selections; } @property (nonatomic, strong) NSMutableArray *photos; @property (nonatomic, strong) NSMutableArray *thumbs; @property (nonatomic, strong) NSMutableArray *assets; @property (nonatomic, strong) ALAssetsLibrary *ALAssetsLibrary; - (void)loadAssets; @end ================================================ FILE: Example/MWPhotoBrowser/Menu.m ================================================ // // Menu.m // MWPhotoBrowser // // Created by Michael Waterfall on 21/10/2010. // Copyright 2010 d3i. All rights reserved. // #import #import "Menu.h" #import "SDImageCache.h" #import "MWCommon.h" @implementation Menu #pragma mark - #pragma mark Initialization - (id)initWithCoder:(NSCoder *)aDecoder { if ((self = [super initWithCoder:aDecoder])) { self.title = @"MWPhotoBrowser"; // Clear cache for testing [[SDImageCache sharedImageCache] clearDisk]; [[SDImageCache sharedImageCache] clearMemory]; _segmentedControl = [[UISegmentedControl alloc] initWithItems:[NSArray arrayWithObjects:@"Push", @"Modal", nil]]; _segmentedControl.selectedSegmentIndex = 0; [_segmentedControl addTarget:self action:@selector(segmentChange) forControlEvents:UIControlEventValueChanged]; UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithCustomView:_segmentedControl]; self.navigationItem.rightBarButtonItem = item; self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Back" style:UIBarButtonItemStyleBordered target:nil action:nil]; [self loadAssets]; } return self; } - (void)segmentChange { [self.tableView reloadData]; } #pragma mark - #pragma mark View - (void)viewDidLoad { [super viewDidLoad]; // Test toolbar hiding // [self setToolbarItems: @[[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:nil action:nil]]]; // [[self navigationController] setToolbarHidden:NO animated:NO]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; // self.navigationController.navigationBar.barTintColor = [UIColor greenColor]; // self.navigationController.navigationBar.translucent = NO; // [self.navigationController setNavigationBarHidden:YES animated:YES]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; // [self.navigationController setNavigationBarHidden:NO animated:YES]; } - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { return YES; } - (BOOL)prefersStatusBarHidden { return NO; } - (UIStatusBarAnimation)preferredStatusBarUpdateAnimation { return UIStatusBarAnimationNone; } #pragma mark - #pragma mark Table view data source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { NSInteger rows = 9; @synchronized(_assets) { if (_assets.count) rows++; } return rows; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { // Create static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier]; } cell.accessoryType = _segmentedControl.selectedSegmentIndex == 0 ? UITableViewCellAccessoryDisclosureIndicator : UITableViewCellAccessoryNone; // Configure switch (indexPath.row) { case 0: { cell.textLabel.text = @"Single photo"; cell.detailTextLabel.text = @"with caption, no grid button"; break; } case 1: { cell.textLabel.text = @"Multiple photos and video"; cell.detailTextLabel.text = @"with captions"; break; } case 2: { cell.textLabel.text = @"Multiple photo grid"; cell.detailTextLabel.text = @"showing grid first, nav arrows enabled"; break; } case 3: { cell.textLabel.text = @"Photo selections"; cell.detailTextLabel.text = @"selection enabled"; break; } case 4: { cell.textLabel.text = @"Photo selection grid"; cell.detailTextLabel.text = @"selection enabled, start at grid"; break; } case 5: { cell.textLabel.text = @"Web photos"; cell.detailTextLabel.text = @"photos from web"; break; } case 6: { cell.textLabel.text = @"Web photo grid"; cell.detailTextLabel.text = @"showing grid first"; break; } case 7: { cell.textLabel.text = @"Single video"; cell.detailTextLabel.text = @"with auto-play"; break; } case 8: { cell.textLabel.text = @"Web videos"; cell.detailTextLabel.text = @"showing grid first"; break; } case 9: { cell.textLabel.text = @"Library photos and videos"; cell.detailTextLabel.text = @"media from device library"; break; } default: break; } return cell; } #pragma mark - #pragma mark Table view delegate - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { // Browser NSMutableArray *photos = [[NSMutableArray alloc] init]; NSMutableArray *thumbs = [[NSMutableArray alloc] init]; MWPhoto *photo, *thumb; BOOL displayActionButton = YES; BOOL displaySelectionButtons = NO; BOOL displayNavArrows = NO; BOOL enableGrid = YES; BOOL startOnGrid = NO; BOOL autoPlayOnAppear = NO; switch (indexPath.row) { case 0: // Photos photo = [MWPhoto photoWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"photo2" ofType:@"jpg"]]]; photo.caption = @"The London Eye is a giant Ferris wheel situated on the banks of the River Thames, in London, England."; [photos addObject:photo]; // Options enableGrid = NO; break; case 1: { // Local Photos and Videos photo = [MWPhoto photoWithImage:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"photo5" ofType:@"jpg"]]]; photo.caption = @"Fireworks"; [photos addObject:photo]; photo = [MWPhoto photoWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"photo2" ofType:@"jpg"]]]; photo.caption = @"The London Eye is a giant Ferris wheel situated on the banks of the River Thames, in London, England."; [photos addObject:photo]; photo = [MWPhoto photoWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"photo3" ofType:@"jpg"]]]; photo.caption = @"York Floods"; [photos addObject:photo]; photo = [MWPhoto photoWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"video_thumb" ofType:@"jpg"]]]; photo.videoURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"video" ofType:@"mp4"]]; photo.caption = @"Big Buck Bunny"; [photos addObject:photo]; photo = [MWPhoto photoWithImage:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"photo4" ofType:@"jpg"]]]; photo.caption = @"Campervan"; [photos addObject:photo]; // Options enableGrid = NO; break; } case 2: { // Photos photo = [MWPhoto photoWithImage:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"photo5" ofType:@"jpg"]]]; photo.caption = @"White Tower"; [photos addObject:photo]; photo = [MWPhoto photoWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"photo2" ofType:@"jpg"]]]; photo.caption = @"The London Eye is a giant Ferris wheel situated on the banks of the River Thames, in London, England."; [photos addObject:photo]; photo = [MWPhoto photoWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"photo3" ofType:@"jpg"]]]; photo.caption = @"York Floods"; [photos addObject:photo]; photo = [MWPhoto photoWithImage:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"photo4" ofType:@"jpg"]]]; photo.caption = @"Campervan"; [photos addObject:photo]; // Thumbs photo = [MWPhoto photoWithImage:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"photo5t" ofType:@"jpg"]]]; [thumbs addObject:photo]; photo = [MWPhoto photoWithImage:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"photo2t" ofType:@"jpg"]]]; [thumbs addObject:photo]; photo = [MWPhoto photoWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"photo3t" ofType:@"jpg"]]]; [thumbs addObject:photo]; photo = [MWPhoto photoWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"photo4t" ofType:@"jpg"]]]; [thumbs addObject:photo]; // Options startOnGrid = YES; displayNavArrows = YES; break; } case 3: case 4: { // Photos photo = [MWPhoto photoWithImage:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"photo4" ofType:@"jpg"]]]; [photos addObject:photo]; photo = [MWPhoto photoWithImage:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"photo1" ofType:@"jpg"]]]; [photos addObject:photo]; photo = [MWPhoto photoWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"photo2" ofType:@"jpg"]]]; [photos addObject:photo]; photo = [MWPhoto photoWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"photo3" ofType:@"jpg"]]]; [photos addObject:photo]; // Thumbs photo = [MWPhoto photoWithImage:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"photo4t" ofType:@"jpg"]]]; [thumbs addObject:photo]; photo = [MWPhoto photoWithImage:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"photo1t" ofType:@"jpg"]]]; [thumbs addObject:photo]; photo = [MWPhoto photoWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"photo2t" ofType:@"jpg"]]]; [thumbs addObject:photo]; photo = [MWPhoto photoWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"photo3t" ofType:@"jpg"]]]; [thumbs addObject:photo]; // Options displayActionButton = NO; displaySelectionButtons = YES; startOnGrid = indexPath.row == 4; enableGrid = NO; break; } case 5: // Photos [photos addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3567/3523321514_371d9ac42f_b.jpg"]]]; [photos addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3629/3339128908_7aecabc34b_b.jpg"]]]; [photos addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3364/3338617424_7ff836d55f_b.jpg"]]]; [photos addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3590/3329114220_5fbc5bc92b_b.jpg"]]]; [photos addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2449/4052876281_6e068ac860_b.jpg"]]]; // Thumbs [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3567/3523321514_371d9ac42f_q.jpg"]]]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3629/3339128908_7aecabc34b_q.jpg"]]]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3364/3338617424_7ff836d55f_q.jpg"]]]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3590/3329114220_5fbc5bc92b_q.jpg"]]]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2449/4052876281_6e068ac860_q.jpg"]]]; // Options break; case 6: // Photos & thumbs photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3779/9522424255_28a5a9d99c_b.jpg"]]; photo.caption = @"Tube"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3779/9522424255_28a5a9d99c_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3777/9522276829_fdea08ffe2_b.jpg"]]; photo.caption = @"Flat White at Elliot's"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3777/9522276829_fdea08ffe2_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm9.static.flickr.com/8379/8530199945_47b386320f_b.jpg"]]; photo.caption = @"Woburn Abbey"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm9.static.flickr.com/8379/8530199945_47b386320f_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm9.static.flickr.com/8364/8268120482_332d61a89e_b.jpg"]]; photo.caption = @"Frosty walk"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm9.static.flickr.com/8364/8268120482_332d61a89e_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm8.static.flickr.com/7109/7604416018_f23733881b_b.jpg"]]; photo.caption = @"Jury's Inn"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm8.static.flickr.com/7109/7604416018_f23733881b_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm7.static.flickr.com/6002/6020924733_b21874f14c_b.jpg"]]; photo.caption = @"Heavy Rain"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm7.static.flickr.com/6002/6020924733_b21874f14c_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm5.static.flickr.com/4012/4501918517_5facf1a8c4_b.jpg"]]; photo.caption = @"iPad Application Sketch Template v1"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm5.static.flickr.com/4012/4501918517_5facf1a8c4_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2667/4072710001_f36316ddc7_b.jpg"]]; photo.caption = @"Grotto of the Madonna"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2667/4072710001_f36316ddc7_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2449/4052876281_6e068ac860_b.jpg"]]; photo.caption = @"Beautiful Eyes"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2449/4052876281_6e068ac860_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3528/4052875665_53e5b4dc61_b.jpg"]]; photo.caption = @"Cousin Portrait"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3528/4052875665_53e5b4dc61_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3520/3846053408_6ecf775a3e_b.jpg"]]; photo.caption = @"iPhone Application Sketch Template v1.3"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3520/3846053408_6ecf775a3e_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3624/3559209373_003152b4fd_b.jpg"]]; photo.caption = @"Door Knocker of Capitanía General"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3624/3559209373_003152b4fd_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3551/3523421738_30455b63e0_b.jpg"]]; photo.caption = @"Parroquia Sta Maria del Mar"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3551/3523421738_30455b63e0_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3224/3523355044_6551552f93_b.jpg"]]; photo.caption = @"Central Atrium in Casa Batlló"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3224/3523355044_6551552f93_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3567/3523321514_371d9ac42f_b.jpg"]]; photo.caption = @"Gaudí's Casa Batlló spiral ceiling"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3567/3523321514_371d9ac42f_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3629/3339128908_7aecabc34b_b.jpg"]]; photo.caption = @"The Royal Albert Hall"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3629/3339128908_7aecabc34b_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3338/3339119002_e0d8ec2f2e_b.jpg"]]; photo.caption = @"Midday & Midnight at the RAH"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3338/3339119002_e0d8ec2f2e_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3364/3338617424_7ff836d55f_b.jpg"]]; photo.caption = @"Westminster Bridge"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3364/3338617424_7ff836d55f_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3604/3328356821_5503b332aa_b.jpg"]]; photo.caption = @"Prime Meridian Sculpture"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3604/3328356821_5503b332aa_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3590/3329114220_5fbc5bc92b_b.jpg"]]; photo.caption = @"Docklands"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3590/3329114220_5fbc5bc92b_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3602/3329107762_64a1454080_b.jpg"]]; photo.caption = @"Planetarium"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3602/3329107762_64a1454080_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3122/3143635211_80b29ab354_b.jpg"]]; photo.caption = @"Eurostar Perspective"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3122/3143635211_80b29ab354_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3091/3144451298_db6f6da3f9_b.jpg"]]; photo.caption = @"The Meeting Place"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3091/3144451298_db6f6da3f9_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3110/3143623585_a12fa172fc_b.jpg"]]; photo.caption = @"Embrace"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3110/3143623585_a12fa172fc_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3107/3143613445_d9562105ea_b.jpg"]]; photo.caption = @"See to the Sky with the Station Saver"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3107/3143613445_d9562105ea_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3203/3144431930_db55ee05a2_b.jpg"]]; photo.caption = @"Sir John Betjeman"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3203/3144431930_db55ee05a2_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3102/3143588227_5d0d806b43_b.jpg"]]; photo.caption = @"St Pancras, London"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3102/3143588227_5d0d806b43_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3194/2987143528_2ee4a9e3cc_b.jpg"]]; photo.caption = @"Shelter from the Storm"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3194/2987143528_2ee4a9e3cc_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3219/2983541189_467dc559ed_b.jpg"]]; photo.caption = @"Alexander, Molly & George"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3219/2983541189_467dc559ed_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3277/2978593618_25a24b5348_b.jpg"]]; photo.caption = @"It's Eerie Underground"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3277/2978593618_25a24b5348_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3043/2977609977_241fe844be_b.jpg"]]; photo.caption = @"VW Camper"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3043/2977609977_241fe844be_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3257/2871822885_890c7d969d_b.jpg"]]; photo.caption = @"York Floods - September 2008"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3257/2871822885_890c7d969d_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3170/2860137277_ecefb94bb9_b.jpg"]]; photo.caption = @"Still Standing"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3170/2860137277_ecefb94bb9_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3209/2799943935_81840a6dcc_b.jpg"]]; photo.caption = @"The Edge of the World"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3209/2799943935_81840a6dcc_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3269/2800788836_8ae7c988a9_b.jpg"]]; photo.caption = @"Beautiful Bark"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3269/2800788836_8ae7c988a9_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3080/2800766694_5c87a0238c_b.jpg"]]; photo.caption = @"What's the name of this flower?"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3080/2800766694_5c87a0238c_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3110/2799879647_f9ee50054e_b.jpg"]]; photo.caption = @"Flamborough Lighthouse"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3110/2799879647_f9ee50054e_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3176/2575404273_6f2f135801_b.jpg"]]; photo.caption = @"Looking into London's Eye"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3176/2575404273_6f2f135801_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3147/2575402639_3e1e60a0e7_b.jpg"]]; photo.caption = @"Large Ben"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3147/2575402639_3e1e60a0e7_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3002/2576229168_276565ac08_b.jpg"]]; photo.caption = @"The Leaning Tower of London"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3002/2576229168_276565ac08_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3290/2530710337_90d3160da0_b.jpg"]]; photo.caption = @"Monkey Features"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3290/2530710337_90d3160da0_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2342/2499163392_0c8125cbf7_b.jpg"]]; photo.caption = @"Metal and Stone"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2342/2499163392_0c8125cbf7_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3241/2499162188_1097d7280f_b.jpg"]]; photo.caption = @"York Minster Interior"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3241/2499162188_1097d7280f_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3162/2499161250_4100c907ee_b.jpg"]]; photo.caption = @"Colour Below a Heart"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3162/2499161250_4100c907ee_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2411/2386560315_c0b237ed0e_b.jpg"]]; photo.caption = @"Tremendous Tulip"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2411/2386560315_c0b237ed0e_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2140/2375307082_ea04b45d8f_b.jpg"]]; photo.caption = @"Rose Reflection"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2140/2375307082_ea04b45d8f_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3035/2375304830_e894d29141_b.jpg"]]; photo.caption = @"Cliffords Tower, York"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3035/2375304830_e894d29141_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3197/2367543868_af828a6543_b.jpg"]]; photo.caption = @"Cog & Chain"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3197/2367543868_af828a6543_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2286/2367542014_6d8145711c_b.jpg"]]; photo.caption = @"Rocket"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2286/2367542014_6d8145711c_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3217/2323979971_3a6209b41e_b.jpg"]]; photo.caption = @"Snowdrops"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3217/2323979971_3a6209b41e_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3182/2324797228_dec69be7b4_b.jpg"]]; photo.caption = @"Castle Howard Fountain"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3182/2324797228_dec69be7b4_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2266/2323974229_e21e0e3fe1_b.jpg"]]; photo.caption = @"Castle Howard House Lines"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2266/2323974229_e21e0e3fe1_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3018/2323969673_e6d9cc74d7_b.jpg"]]; photo.caption = @"Castle Howard Wide"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3018/2323969673_e6d9cc74d7_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3103/2323967485_694a897d5f_b.jpg"]]; photo.caption = @"Castle Howard House"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3103/2323967485_694a897d5f_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3086/2324784818_8cd6123633_b.jpg"]]; photo.caption = @"Castle Howard Fountain"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3086/2324784818_8cd6123633_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2318/2324783136_56bed1f7ab_b.jpg"]]; photo.caption = @"Castle Howard House Back"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2318/2324783136_56bed1f7ab_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2228/2323959487_113c1c26fe_b.jpg"]]; photo.caption = @"Castle Howard House Side"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2228/2323959487_113c1c26fe_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3058/2324776658_da476bbb32_b.jpg"]]; photo.caption = @"Castle Howard"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3058/2324776658_da476bbb32_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2359/2323944883_a277e1becf_b.jpg"]]; photo.caption = @"Peacock"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2359/2323944883_a277e1becf_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2172/2323940209_ae2d69fb51_b.jpg"]]; photo.caption = @"Castle Howard Fountain"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2172/2323940209_ae2d69fb51_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2143/2268578601_464af2fabc_b.jpg"]]; photo.caption = @"Outlook"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2143/2268578601_464af2fabc_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2165/2268575867_d3c1bc5b65_b.jpg"]]; photo.caption = @"Stones & Sand"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2165/2268575867_d3c1bc5b65_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2149/2269364904_bc4a63f3e0_b.jpg"]]; photo.caption = @"Pebbles and Stones"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2149/2269364904_bc4a63f3e0_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2176/2268569547_99197322f9_b.jpg"]]; photo.caption = @"Fisherman"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2176/2268569547_99197322f9_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2095/2268567981_ac142a0409_b.jpg"]]; photo.caption = @"Walking on Water"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2095/2268567981_ac142a0409_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2074/2268528659_e7f1d60e8f_b.jpg"]]; photo.caption = @"Viking Boat York"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2074/2268528659_e7f1d60e8f_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2326/2268524733_5f40d784d4_b.jpg"]]; photo.caption = @"Hot Air Balloon Colours"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2326/2268524733_5f40d784d4_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2022/2268522293_470659cdec_b.jpg"]]; photo.caption = @"Hot Air Balloon Lift Off"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2022/2268522293_470659cdec_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2420/2268520049_d33bb30b6f_b.jpg"]]; photo.caption = @"Hot Air Balloon Take Off"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2420/2268520049_d33bb30b6f_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2403/2269308580_24e5e8cb1d_b.jpg"]]; photo.caption = @"Hot Air Balloon High"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2403/2269308580_24e5e8cb1d_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2040/2268515857_7708793db8_b.jpg"]]; photo.caption = @"Hot Air Balloon Blue Sky"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2040/2268515857_7708793db8_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2176/2137556633_ce7f55d97c_b.jpg"]]; photo.caption = @"Christmas Lights"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2176/2137556633_ce7f55d97c_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2256/2138335766_532c98183b_b.jpg"]]; photo.caption = @"Christmas Dinner"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2256/2138335766_532c98183b_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2309/2137552857_b3a866d66a_b.jpg"]]; photo.caption = @"Christmas Tree"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2309/2137552857_b3a866d66a_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2151/2137550333_30a80de9dd_b.jpg"]]; photo.caption = @"Christmas Gifts"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2151/2137550333_30a80de9dd_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2217/1800632013_f5b6f430ea_b.jpg"]]; photo.caption = @"Firework Flower"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2217/1800632013_f5b6f430ea_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2025/1800630921_05c119b257_b.jpg"]]; photo.caption = @"Fireworks 1"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2025/1800630921_05c119b257_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2283/1800622021_a69274fe8e_b.jpg"]]; photo.caption = @"Sunset"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2283/1800622021_a69274fe8e_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2296/1800493695_05e8f99119_b.jpg"]]; photo.caption = @"Morning Fields"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm3.static.flickr.com/2296/1800493695_05e8f99119_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1318/1258684849_5423b2b0a7_b.jpg"]]; photo.caption = @"Garden Window"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1318/1258684849_5423b2b0a7_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1169/1214182813_92ef4b864e_b.jpg"]]; photo.caption = @"Storm Clouds"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1169/1214182813_92ef4b864e_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1060/1214995776_c6ccc7b589_b.jpg"]]; photo.caption = @"South Light"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1060/1214995776_c6ccc7b589_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1271/1213967453_8f5927b62a_b.jpg"]]; photo.caption = @"Cala Gal Dana, Panoramic"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1271/1213967453_8f5927b62a_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1052/1214735762_1fa7af8cf9_b.jpg"]]; photo.caption = @"Coloured Glass"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1052/1214735762_1fa7af8cf9_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1108/1214725784_392c7236cb_b.jpg"]]; photo.caption = @"Well"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1108/1214725784_392c7236cb_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1261/1214721656_3e50b51adb_b.jpg"]]; photo.caption = @"Ciutadella"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1261/1214721656_3e50b51adb_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1422/1213857765_dd59feadff_b.jpg"]]; photo.caption = @"Columbus"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1422/1213857765_dd59feadff_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1075/1214720084_9ec6163320_b.jpg"]]; photo.caption = @"Watch Tower"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1075/1214720084_9ec6163320_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1413/1213849449_689f6c5b34_b.jpg"]]; photo.caption = @"White Tower"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1413/1213849449_689f6c5b34_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1310/1213846529_9a1fc08f0f_b.jpg"]]; photo.caption = @"White & Blue"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1310/1213846529_9a1fc08f0f_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1320/1213843939_6f5594ffca_b.jpg"]]; photo.caption = @"Jesus, Monte Toro"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1320/1213843939_6f5594ffca_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1290/1213774167_804edea2a8_b.jpg"]]; photo.caption = @"York Minster"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1290/1213774167_804edea2a8_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1340/1214632114_7587edf8dc_b.jpg"]]; photo.caption = @"York Minster"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1340/1214632114_7587edf8dc_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1126/1213763123_b2e0ad8954_b.jpg"]]; photo.caption = @"York Minster"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1126/1213763123_b2e0ad8954_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1157/1213707107_43444cf13b_b.jpg"]]; photo.caption = @"Water Plant"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1157/1213707107_43444cf13b_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1033/1082797343_87d812258f_b.jpg"]]; photo.caption = @"Micklegate, York"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1033/1082797343_87d812258f_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1106/1083643878_f9082c3f58_b.jpg"]]; photo.caption = @"Tea Rooms"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1106/1083643878_f9082c3f58_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1188/1082780169_3d189a56d5_b.jpg"]]; photo.caption = @"York Minster"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1188/1082780169_3d189a56d5_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1274/1083627286_9bb558047b_b.jpg"]]; photo.caption = @"Constantine"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1274/1083627286_9bb558047b_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1276/1082759563_41eb4412d7_b.jpg"]]; photo.caption = @"York Minster"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1276/1082759563_41eb4412d7_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1326/1082740813_e6c9b5fc87_b.jpg"]]; photo.caption = @"I Do"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1326/1082740813_e6c9b5fc87_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1407/1083588060_c10e3abcb3_b.jpg"]]; photo.caption = @"Hanging Basket"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1407/1083588060_c10e3abcb3_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1268/1083499110_80bfba3a27_b.jpg"]]; photo.caption = @"Hover Fly"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1268/1083499110_80bfba3a27_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1022/1082562763_03ac6b462a_b.jpg"]]; photo.caption = @"Pylon"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1022/1082562763_03ac6b462a_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1154/1083348824_790abf6c45_b.jpg"]]; photo.caption = @"Drenched Cars"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1154/1083348824_790abf6c45_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1356/1082760752_204933b13c_b.jpg"]]; photo.caption = @"Wedding Cake"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1356/1082760752_204933b13c_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1310/1081997284_f45d03e3e9_b.jpg"]]; photo.caption = @"Wedding Rings"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1310/1081997284_f45d03e3e9_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1235/1080940411_1dbfd6e577_b.jpg"]]; photo.caption = @"Brides Mother"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1235/1080940411_1dbfd6e577_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1352/1041732121_d1a1dab44b_b.jpg"]]; photo.caption = @"Wedding Roses"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1352/1041732121_d1a1dab44b_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1246/1041698487_fd91c19893_b.jpg"]]; photo.caption = @"Butterfly Bridesmaid"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1246/1041698487_fd91c19893_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1168/1042508712_877068e029_b.jpg"]]; photo.caption = @"Bridesmaid Stairs"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1168/1042508712_877068e029_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1406/1037917833_087badcaaf_b.jpg"]]; photo.caption = @"Red & White Night"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1406/1037917833_087badcaaf_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1202/1037911739_a2d684d0d3_b.jpg"]]; photo.caption = @"Red & White Night Portrait"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1202/1037911739_a2d684d0d3_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1336/1038760638_42b348543d_b.jpg"]]; photo.caption = @"Red & White Night Sign"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1336/1038760638_42b348543d_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1182/1038749330_6eeceea376_b.jpg"]]; photo.caption = @"White Whip Light"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1182/1038749330_6eeceea376_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1121/1038747138_ed4388600e_b.jpg"]]; photo.caption = @"Red Long Exp"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1121/1038747138_ed4388600e_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1174/1037890363_5918979b27_b.jpg"]]; photo.caption = @"M6 Sign"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1174/1037890363_5918979b27_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1160/1037797119_e535914f06_b.jpg"]]; photo.caption = @"Sunlit Leaf"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1160/1037797119_e535914f06_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1037/1037789909_d320b9d759_b.jpg"]]; photo.caption = @"Water Covered Leaf"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1037/1037789909_d320b9d759_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1293/1037658807_048f125b28_b.jpg"]]; photo.caption = @"Transparent Light Leaf"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1293/1037658807_048f125b28_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1111/1038457510_2aa6edc766_b.jpg"]]; photo.caption = @"Winter Nettle"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1111/1038457510_2aa6edc766_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1418/1037593249_87a705a0e9_b.jpg"]]; photo.caption = @"Frost Edged Leaf"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1418/1037593249_87a705a0e9_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1416/1038320230_14bb31307c_b.jpg"]]; photo.caption = @"Large Sunset"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1416/1038320230_14bb31307c_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1382/1038307382_19d06a3c7f_b.jpg"]]; photo.caption = @"Band Stand at Night"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1382/1038307382_19d06a3c7f_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1187/1037444897_9c6bb617bd_b.jpg"]]; photo.caption = @"Stafford Street at Night"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1187/1037444897_9c6bb617bd_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1235/1020112582_892713cc72_b.jpg"]]; photo.caption = @"Villa"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1235/1020112582_892713cc72_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1438/1019238601_78d489ab5d_b.jpg"]]; photo.caption = @"Offshore Silhouette 2"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1438/1019238601_78d489ab5d_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1234/1020080322_572763d9f1_b.jpg"]]; photo.caption = @"Perfect Circle"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1234/1020080322_572763d9f1_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1016/1019200909_574df6bf54_b.jpg"]]; photo.caption = @"VW Golf"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1016/1019200909_574df6bf54_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1114/1020056296_c4dc0d50b1_b.jpg"]]; photo.caption = @"Spanish Villa House"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1114/1020056296_c4dc0d50b1_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1197/1020049432_4db0f8efd5_b.jpg"]]; photo.caption = @"Bend in the Road"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1197/1020049432_4db0f8efd5_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1189/1020036286_f8cf41ac69_b.jpg"]]; photo.caption = @"DoF Blue Chair"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1189/1020036286_f8cf41ac69_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1267/1019167123_e55e1bc56f_b.jpg"]]; photo.caption = @"Villa"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1267/1019167123_e55e1bc56f_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1116/1019157385_8faf3e3573_b.jpg"]]; photo.caption = @"Yellow Flower"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1116/1019157385_8faf3e3573_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1191/1019151951_774b7b5402_b.jpg"]]; photo.caption = @"Villa"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1191/1019151951_774b7b5402_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1363/1019139793_b55ce199d0_b.jpg"]]; photo.caption = @"Luxury Spa at La Manga Club"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1363/1019139793_b55ce199d0_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1038/1019127559_0d0a57733f_b.jpg"]]; photo.caption = @"Lonely Boat"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1038/1019127559_0d0a57733f_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1329/1019973422_b575f4961c_b.jpg"]]; photo.caption = @"Garden Chair"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1329/1019973422_b575f4961c_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1168/1019745098_545403ede6_b.jpg"]]; photo.caption = @"Rust for Sale!"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1168/1019745098_545403ede6_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1394/1019735146_8875d55079_b.jpg"]]; photo.caption = @"Safety"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1394/1019735146_8875d55079_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1223/1019727992_af62bf470d_b.jpg"]]; photo.caption = @"Monster VW"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1223/1019727992_af62bf470d_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1297/1019721148_796385dcb6_b.jpg"]]; photo.caption = @"Yeah it is!"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1297/1019721148_796385dcb6_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1093/1018853687_a162a984ca_b.jpg"]]; photo.caption = @"VDub Little Buggers Club"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1093/1018853687_a162a984ca_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1069/1018846077_9576590764_b.jpg"]]; photo.caption = @"Shiny Alloys"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1069/1018846077_9576590764_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1229/1018838599_3f581eb852_b.jpg"]]; photo.caption = @"VW Headlights"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1229/1018838599_3f581eb852_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1187/1018830961_a4f97ea5a7_b.jpg"]]; photo.caption = @"VW Vinyl"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1187/1018830961_a4f97ea5a7_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1224/1019677202_116cc992e5_b.jpg"]]; photo.caption = @"VW Dude"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1224/1019677202_116cc992e5_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1207/1019670340_a6268b294a_b.jpg"]]; photo.caption = @"Large VW Logo"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1207/1019670340_a6268b294a_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1275/1018793735_a4172e3675_b.jpg"]]; photo.caption = @"IMG_1781"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1275/1018793735_a4172e3675_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1129/1018777423_1238c3249a_b.jpg"]]; photo.caption = @"IMG_1770"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1129/1018777423_1238c3249a_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1144/1019556448_4cd8a965f5_b.jpg"]]; photo.caption = @"Piano DoF"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1144/1019556448_4cd8a965f5_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1373/1018689833_03ae27b7c9_b.jpg"]]; photo.caption = @"Festive Ice"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1373/1018689833_03ae27b7c9_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1132/1019542722_3723ac58f2_b.jpg"]]; photo.caption = @"Festivities"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1132/1019542722_3723ac58f2_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1162/1019517888_7982bcd84f_b.jpg"]]; photo.caption = @"Christmas Lights"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1162/1019517888_7982bcd84f_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1081/1019239012_d174b5daf3_b.jpg"]]; photo.caption = @"Water Steps"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1081/1019239012_d174b5daf3_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1241/1019218632_10255f1d43_b.jpg"]]; photo.caption = @"Water Steps Frozen in Time"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1241/1019218632_10255f1d43_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1029/1018206545_6e8e0ec1e5_b.jpg"]]; photo.caption = @"Daffo your Dill"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1029/1018206545_6e8e0ec1e5_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1261/1018910192_48fe1f5b6b_b.jpg"]]; photo.caption = @"Cock"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1261/1018910192_48fe1f5b6b_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1228/1017960551_1a3d1b0a9c_b.jpg"]]; photo.caption = @"Fountain CU"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1228/1017960551_1a3d1b0a9c_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1077/1017926975_1c9e8953e3_b.jpg"]]; photo.caption = @"Chatsworth"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1077/1017926975_1c9e8953e3_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1213/1018743552_7d0c410416_b.jpg"]]; photo.caption = @"Gardins"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1213/1018743552_7d0c410416_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1342/1017867885_5e90984ce5_b.jpg"]]; photo.caption = @"Water Sun Steps"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1342/1017867885_5e90984ce5_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1047/1018622228_056b0dfcbf_b.jpg"]]; photo.caption = @"Daffodil CU"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1047/1018622228_056b0dfcbf_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1415/1018615064_937b34cca7_b.jpg"]]; photo.caption = @"Twin Daffodil"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1415/1018615064_937b34cca7_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1436/1017097897_73a265e346_b.jpg"]]; photo.caption = @"Bird Sculpture"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1436/1017097897_73a265e346_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1004/1017051687_e281a77270_b.jpg"]]; photo.caption = @"Flower with Water Drops"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1004/1017051687_e281a77270_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1150/1017031825_53ba6f6ab9_b.jpg"]]; photo.caption = @"Bats"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1150/1017031825_53ba6f6ab9_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1381/1017008087_c0eb892b47_b.jpg"]]; photo.caption = @"Stream Slow"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1381/1017008087_c0eb892b47_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1067/1017708680_82a427f18e_b.jpg"]]; photo.caption = @"Dog Sculpture"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1067/1017708680_82a427f18e_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1438/1016801251_4817ff3b67_b.jpg"]]; photo.caption = @"Beads of Water on Grass"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1438/1016801251_4817ff3b67_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1398/1017654416_d9e7ad4370_b.jpg"]]; photo.caption = @"Beads of Water on Grass CU"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1398/1017654416_d9e7ad4370_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1035/1011726328_79c46b6af3_b.jpg"]]; photo.caption = @"Global Fence"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1035/1011726328_79c46b6af3_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1129/1011721614_8a5de7701c_b.jpg"]]; photo.caption = @"Barb Wire Fence"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1129/1011721614_8a5de7701c_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1152/1010860101_e9ac61fe2c_b.jpg"]]; photo.caption = @"Barb Wire"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1152/1010860101_e9ac61fe2c_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1276/1011685542_9e5bc95aaf_b.jpg"]]; photo.caption = @"Church Light"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1276/1011685542_9e5bc95aaf_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1008/1011667622_94607012d3_b.jpg"]]; photo.caption = @"My Car in Snow"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1008/1011667622_94607012d3_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1408/1011656768_62713e265a_b.jpg"]]; photo.caption = @"Frost"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1408/1011656768_62713e265a_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1351/1011575570_433c62399d_b.jpg"]]; photo.caption = @"Memorial"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1351/1011575570_433c62399d_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1284/1011559728_ad59fa81ce_b.jpg"]]; photo.caption = @"Cracked Cloud Sun"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1284/1011559728_ad59fa81ce_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1045/1010555983_6cca0484e9_b.jpg"]]; photo.caption = @"Frosted Grass CU"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1045/1010555983_6cca0484e9_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1300/1010519417_00b75cc013_b.jpg"]]; photo.caption = @"Water Leaves"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1300/1010519417_00b75cc013_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1227/1010469785_4111f8b6ae_b.jpg"]]; photo.caption = @"Sunset Tree"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1227/1010469785_4111f8b6ae_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1099/1011324558_c8f4802d4b_b.jpg"]]; photo.caption = @"Sunset"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1099/1011324558_c8f4802d4b_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1224/1011283712_5750c5ba8e_b.jpg"]]; photo.caption = @"Mannequin Half Light"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1224/1011283712_5750c5ba8e_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1384/1010423817_56b81c6f24_b.jpg"]]; photo.caption = @"Mannequin Side Light"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1384/1010423817_56b81c6f24_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1429/1011276778_e97457682a_b.jpg"]]; photo.caption = @"Mannequin"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1429/1011276778_e97457682a_q.jpg"]]]; photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1235/1010416375_fe91e5ce22_b.jpg"]]; photo.caption = @"Mannequin DoF"; [photos addObject:photo]; [thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm2.static.flickr.com/1235/1010416375_fe91e5ce22_q.jpg"]]]; // Options startOnGrid = YES; break; case 7: { // Single video photo = [MWPhoto photoWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"video_thumb" ofType:@"jpg"]]]; photo.videoURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"video" ofType:@"mp4"]]; [photos addObject:photo]; enableGrid = NO; autoPlayOnAppear = YES; break; } case 8: { // Videos photo = [MWPhoto photoWithURL:[[NSURL alloc] initWithString:@"https://scontent.cdninstagram.com/hphotos-xpt1/t51.2885-15/e15/11192696_824079697688618_1761661_n.jpg"]]; photo.videoURL = [[NSURL alloc] initWithString:@"https://scontent.cdninstagram.com/hphotos-xpa1/t50.2886-16/11200303_1440130956287424_1714699187_n.mp4"]; [photos addObject:photo]; thumb = [MWPhoto photoWithURL:[[NSURL alloc] initWithString:@"https://scontent.cdninstagram.com/hphotos-xpt1/t51.2885-15/s150x150/e15/11192696_824079697688618_1761661_n.jpg"]]; thumb.isVideo = YES; [thumbs addObject:thumb]; // Test video with no poster frame // photo = [MWPhoto videoWithURL:[[NSURL alloc] initWithString:@"https://scontent.cdninstagram.com/hphotos-xfa1/t50.2886-16/11237510_945154435524423_2137519922_n.mp4"]]; // [photos addObject:photo]; // thumb = [MWPhoto new]; // thumb.isVideo = YES; // [thumbs addObject:thumb]; photo = [MWPhoto photoWithURL:[[NSURL alloc] initWithString:@"https://scontent.cdninstagram.com/hphotos-xaf1/t51.2885-15/e15/11240463_963135443745570_1519872157_n.jpg"]]; photo.videoURL = [[NSURL alloc] initWithString:@"https://scontent.cdninstagram.com/hphotos-xfa1/t50.2886-16/11237510_945154435524423_2137519922_n.mp4"]; [photos addObject:photo]; thumb = [MWPhoto photoWithURL:[[NSURL alloc] initWithString:@"https://scontent.cdninstagram.com/hphotos-xaf1/t51.2885-15/s150x150/e15/11240463_963135443745570_1519872157_n.jpg"]]; thumb.isVideo = YES; [thumbs addObject:thumb]; photo = [MWPhoto photoWithURL:[[NSURL alloc] initWithString:@"https://scontent.cdninstagram.com/hphotos-xaf1/t51.2885-15/e15/11313531_1625089227727682_169403963_n.jpg"]]; photo.videoURL = [[NSURL alloc] initWithString:@"https://scontent.cdninstagram.com/hphotos-xfa1/t50.2886-16/11336249_1783839318509644_116225363_n.mp4"]; [photos addObject:photo]; thumb = [MWPhoto photoWithURL:[[NSURL alloc] initWithString:@"https://scontent.cdninstagram.com/hphotos-xaf1/t51.2885-15/s150x150/e15/11313531_1625089227727682_169403963_n.jpg"]]; thumb.isVideo = YES; [thumbs addObject:thumb]; // Options startOnGrid = YES; break; } case 9: { @synchronized(_assets) { NSMutableArray *copy = [_assets copy]; if (NSClassFromString(@"PHAsset")) { // Photos library UIScreen *screen = [UIScreen mainScreen]; CGFloat scale = screen.scale; // Sizing is very rough... more thought required in a real implementation CGFloat imageSize = MAX(screen.bounds.size.width, screen.bounds.size.height) * 1.5; CGSize imageTargetSize = CGSizeMake(imageSize * scale, imageSize * scale); CGSize thumbTargetSize = CGSizeMake(imageSize / 3.0 * scale, imageSize / 3.0 * scale); for (PHAsset *asset in copy) { [photos addObject:[MWPhoto photoWithAsset:asset targetSize:imageTargetSize]]; [thumbs addObject:[MWPhoto photoWithAsset:asset targetSize:thumbTargetSize]]; } } else { // Assets library for (ALAsset *asset in copy) { MWPhoto *photo = [MWPhoto photoWithURL:asset.defaultRepresentation.url]; [photos addObject:photo]; MWPhoto *thumb = [MWPhoto photoWithImage:[UIImage imageWithCGImage:asset.thumbnail]]; [thumbs addObject:thumb]; if ([asset valueForProperty:ALAssetPropertyType] == ALAssetTypeVideo) { photo.videoURL = asset.defaultRepresentation.url; thumb.isVideo = true; } } } } break; } default: break; } self.photos = photos; self.thumbs = thumbs; // Create browser MWPhotoBrowser *browser = [[MWPhotoBrowser alloc] initWithDelegate:self]; browser.displayActionButton = displayActionButton; browser.displayNavArrows = displayNavArrows; browser.displaySelectionButtons = displaySelectionButtons; browser.alwaysShowControls = displaySelectionButtons; browser.zoomPhotosToFill = YES; browser.enableGrid = enableGrid; browser.startOnGrid = startOnGrid; browser.enableSwipeToDismiss = NO; browser.autoPlayOnAppear = autoPlayOnAppear; [browser setCurrentPhotoIndex:0]; // Test custom selection images // browser.customImageSelectedIconName = @"ImageSelected.png"; // browser.customImageSelectedSmallIconName = @"ImageSelectedSmall.png"; // Reset selections if (displaySelectionButtons) { _selections = [NSMutableArray new]; for (int i = 0; i < photos.count; i++) { [_selections addObject:[NSNumber numberWithBool:NO]]; } } // Show if (_segmentedControl.selectedSegmentIndex == 0) { // Push [self.navigationController pushViewController:browser animated:YES]; } else { // Modal UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:browser]; nc.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; [self presentViewController:nc animated:YES completion:nil]; } // Release // Deselect [self.tableView deselectRowAtIndexPath:indexPath animated:YES]; // Test reloading of data after delay double delayInSeconds = 3; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ // // Test removing an object // [_photos removeLastObject]; // [browser reloadData]; // // // Test all new // [_photos removeAllObjects]; // [_photos addObject:[MWPhoto photoWithFilePath:[[NSBundle mainBundle] pathForResource:@"photo3" ofType:@"jpg"]]]; // [browser reloadData]; // // // Test changing photo index // [browser setCurrentPhotoIndex:9]; // // Test updating selections // _selections = [NSMutableArray new]; // for (int i = 0; i < [self numberOfPhotosInPhotoBrowser:browser]; i++) { // [_selections addObject:[NSNumber numberWithBool:YES]]; // } // [browser reloadData]; }); } #pragma mark - MWPhotoBrowserDelegate - (NSUInteger)numberOfPhotosInPhotoBrowser:(MWPhotoBrowser *)photoBrowser { return _photos.count; } - (id )photoBrowser:(MWPhotoBrowser *)photoBrowser photoAtIndex:(NSUInteger)index { if (index < _photos.count) return [_photos objectAtIndex:index]; return nil; } - (id )photoBrowser:(MWPhotoBrowser *)photoBrowser thumbPhotoAtIndex:(NSUInteger)index { if (index < _thumbs.count) return [_thumbs objectAtIndex:index]; return nil; } //- (MWCaptionView *)photoBrowser:(MWPhotoBrowser *)photoBrowser captionViewForPhotoAtIndex:(NSUInteger)index { // MWPhoto *photo = [self.photos objectAtIndex:index]; // MWCaptionView *captionView = [[MWCaptionView alloc] initWithPhoto:photo]; // return [captionView autorelease]; //} //- (void)photoBrowser:(MWPhotoBrowser *)photoBrowser actionButtonPressedForPhotoAtIndex:(NSUInteger)index { // NSLog(@"ACTION!"); //} - (void)photoBrowser:(MWPhotoBrowser *)photoBrowser didDisplayPhotoAtIndex:(NSUInteger)index { NSLog(@"Did start viewing photo at index %lu", (unsigned long)index); } - (BOOL)photoBrowser:(MWPhotoBrowser *)photoBrowser isPhotoSelectedAtIndex:(NSUInteger)index { return [[_selections objectAtIndex:index] boolValue]; } //- (NSString *)photoBrowser:(MWPhotoBrowser *)photoBrowser titleForPhotoAtIndex:(NSUInteger)index { // return [NSString stringWithFormat:@"Photo %lu", (unsigned long)index+1]; //} - (void)photoBrowser:(MWPhotoBrowser *)photoBrowser photoAtIndex:(NSUInteger)index selectedChanged:(BOOL)selected { [_selections replaceObjectAtIndex:index withObject:[NSNumber numberWithBool:selected]]; NSLog(@"Photo at index %lu selected %@", (unsigned long)index, selected ? @"YES" : @"NO"); } - (void)photoBrowserDidFinishModalPresentation:(MWPhotoBrowser *)photoBrowser { // If we subscribe to this method we must dismiss the view controller ourselves NSLog(@"Did finish modal presentation"); [self dismissViewControllerAnimated:YES completion:nil]; } #pragma mark - Load Assets - (void)loadAssets { if (NSClassFromString(@"PHAsset")) { // Check library permissions PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus]; if (status == PHAuthorizationStatusNotDetermined) { [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { if (status == PHAuthorizationStatusAuthorized) { [self performLoadAssets]; } }]; } else if (status == PHAuthorizationStatusAuthorized) { [self performLoadAssets]; } } else { // Assets library [self performLoadAssets]; } } - (void)performLoadAssets { // Initialise _assets = [NSMutableArray new]; // Load if (NSClassFromString(@"PHAsset")) { // Photos library iOS >= 8 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ PHFetchOptions *options = [PHFetchOptions new]; options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]]; PHFetchResult *fetchResults = [PHAsset fetchAssetsWithOptions:options]; [fetchResults enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { [_assets addObject:obj]; }]; if (fetchResults.count > 0) { [self.tableView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO]; } }); } else { // Assets Library iOS < 8 _ALAssetsLibrary = [[ALAssetsLibrary alloc] init]; // Run in the background as it takes a while to get all assets from the library dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSMutableArray *assetGroups = [[NSMutableArray alloc] init]; NSMutableArray *assetURLDictionaries = [[NSMutableArray alloc] init]; // Process assets void (^assetEnumerator)(ALAsset *, NSUInteger, BOOL *) = ^(ALAsset *result, NSUInteger index, BOOL *stop) { if (result != nil) { NSString *assetType = [result valueForProperty:ALAssetPropertyType]; if ([assetType isEqualToString:ALAssetTypePhoto] || [assetType isEqualToString:ALAssetTypeVideo]) { [assetURLDictionaries addObject:[result valueForProperty:ALAssetPropertyURLs]]; NSURL *url = result.defaultRepresentation.url; [_ALAssetsLibrary assetForURL:url resultBlock:^(ALAsset *asset) { if (asset) { @synchronized(_assets) { [_assets addObject:asset]; if (_assets.count == 1) { // Added first asset so reload data [self.tableView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO]; } } } } failureBlock:^(NSError *error){ NSLog(@"operation was not successfull!"); }]; } } }; // Process groups void (^ assetGroupEnumerator) (ALAssetsGroup *, BOOL *) = ^(ALAssetsGroup *group, BOOL *stop) { if (group != nil) { [group enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:assetEnumerator]; [assetGroups addObject:group]; } }; // Process! [_ALAssetsLibrary enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:assetGroupEnumerator failureBlock:^(NSError *error) { NSLog(@"There is an error"); }]; }); } } @end ================================================ FILE: Example/MWPhotoBrowser/en.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ ================================================ FILE: Example/MWPhotoBrowser/main.m ================================================ // // main.m // MWPhotoBrowser // // Created by Michael Waterfall on 07/04/2015. // Copyright (c) 2015 Michael Waterfall. All rights reserved. // @import UIKit; #import "AppDelegate.h" int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } ================================================ FILE: Example/MWPhotoBrowser.xcodeproj/project.pbxproj ================================================ archiveVersion 1 classes objectVersion 46 objects 02040E41C46EB311FEA82F90 buildActionMask 2147483647 files inputPaths isa PBXShellScriptBuildPhase name Embed Pods Frameworks outputPaths runOnlyForDeploymentPostprocessing 0 shellPath /bin/sh shellScript "${SRCROOT}/Pods/Target Support Files/Pods-MWPhotoBrowser_Tests/Pods-MWPhotoBrowser_Tests-frameworks.sh" showEnvVarsInLog 0 0EC5D4A41B4C46E50092ACC1 isa PBXFileReference lastKnownFileType image.jpeg name photo1.jpg path MWPhotoBrowser/Local Media/photo1.jpg sourceTree SOURCE_ROOT 0EC5D4A51B4C46E50092ACC1 isa PBXFileReference lastKnownFileType image.jpeg name photo1t.jpg path MWPhotoBrowser/Local Media/photo1t.jpg sourceTree SOURCE_ROOT 0EC5D4A61B4C46E50092ACC1 isa PBXFileReference lastKnownFileType image.jpeg name photo2.jpg path MWPhotoBrowser/Local Media/photo2.jpg sourceTree SOURCE_ROOT 0EC5D4A71B4C46E50092ACC1 isa PBXFileReference lastKnownFileType image.jpeg name photo2t.jpg path MWPhotoBrowser/Local Media/photo2t.jpg sourceTree SOURCE_ROOT 0EC5D4A81B4C46E50092ACC1 isa PBXFileReference lastKnownFileType image.jpeg name photo3.jpg path MWPhotoBrowser/Local Media/photo3.jpg sourceTree SOURCE_ROOT 0EC5D4A91B4C46E50092ACC1 isa PBXFileReference lastKnownFileType image.jpeg name photo3t.jpg path MWPhotoBrowser/Local Media/photo3t.jpg sourceTree SOURCE_ROOT 0EC5D4AA1B4C46E50092ACC1 isa PBXFileReference lastKnownFileType image.jpeg name photo4.jpg path MWPhotoBrowser/Local Media/photo4.jpg sourceTree SOURCE_ROOT 0EC5D4AB1B4C46E50092ACC1 isa PBXFileReference lastKnownFileType image.jpeg name photo4t.jpg path MWPhotoBrowser/Local Media/photo4t.jpg sourceTree SOURCE_ROOT 0EC5D4AC1B4C46E50092ACC1 isa PBXFileReference lastKnownFileType image.jpeg name photo5.jpg path MWPhotoBrowser/Local Media/photo5.jpg sourceTree SOURCE_ROOT 0EC5D4AD1B4C46E50092ACC1 isa PBXFileReference lastKnownFileType image.jpeg name photo5t.jpg path MWPhotoBrowser/Local Media/photo5t.jpg sourceTree SOURCE_ROOT 0EC5D4AE1B4C46E50092ACC1 isa PBXFileReference lastKnownFileType image.jpeg name photo6.jpg path MWPhotoBrowser/Local Media/photo6.jpg sourceTree SOURCE_ROOT 0EC5D4AF1B4C46E50092ACC1 isa PBXFileReference lastKnownFileType image.jpeg name photo6t.jpg path MWPhotoBrowser/Local Media/photo6t.jpg sourceTree SOURCE_ROOT 0EC5D4B01B4C46E50092ACC1 isa PBXFileReference lastKnownFileType image.jpeg name photo7.jpg path MWPhotoBrowser/Local Media/photo7.jpg sourceTree SOURCE_ROOT 0EC5D4B11B4C46E50092ACC1 isa PBXFileReference lastKnownFileType image.jpeg name photo7t.jpg path MWPhotoBrowser/Local Media/photo7t.jpg sourceTree SOURCE_ROOT 0EC5D4B21B4C46E50092ACC1 isa PBXFileReference lastKnownFileType image.jpeg name photo8.jpg path MWPhotoBrowser/Local Media/photo8.jpg sourceTree SOURCE_ROOT 0EC5D4B31B4C46E50092ACC1 isa PBXFileReference lastKnownFileType image.jpeg name photo8t.jpg path MWPhotoBrowser/Local Media/photo8t.jpg sourceTree SOURCE_ROOT 0EC5D4B41B4C46E50092ACC1 isa PBXFileReference lastKnownFileType file name video.mp4 path MWPhotoBrowser/Local Media/video.mp4 sourceTree SOURCE_ROOT 0EC5D4B51B4C46E50092ACC1 fileRef 0EC5D4A41B4C46E50092ACC1 isa PBXBuildFile 0EC5D4B61B4C46E50092ACC1 fileRef 0EC5D4A51B4C46E50092ACC1 isa PBXBuildFile 0EC5D4B71B4C46E50092ACC1 fileRef 0EC5D4A61B4C46E50092ACC1 isa PBXBuildFile 0EC5D4B81B4C46E50092ACC1 fileRef 0EC5D4A71B4C46E50092ACC1 isa PBXBuildFile 0EC5D4B91B4C46E50092ACC1 fileRef 0EC5D4A81B4C46E50092ACC1 isa PBXBuildFile 0EC5D4BA1B4C46E50092ACC1 fileRef 0EC5D4A91B4C46E50092ACC1 isa PBXBuildFile 0EC5D4BB1B4C46E50092ACC1 fileRef 0EC5D4AA1B4C46E50092ACC1 isa PBXBuildFile 0EC5D4BC1B4C46E50092ACC1 fileRef 0EC5D4AB1B4C46E50092ACC1 isa PBXBuildFile 0EC5D4BD1B4C46E50092ACC1 fileRef 0EC5D4AC1B4C46E50092ACC1 isa PBXBuildFile 0EC5D4BE1B4C46E50092ACC1 fileRef 0EC5D4AD1B4C46E50092ACC1 isa PBXBuildFile 0EC5D4BF1B4C46E50092ACC1 fileRef 0EC5D4AE1B4C46E50092ACC1 isa PBXBuildFile 0EC5D4C01B4C46E50092ACC1 fileRef 0EC5D4AF1B4C46E50092ACC1 isa PBXBuildFile 0EC5D4C11B4C46E50092ACC1 fileRef 0EC5D4B01B4C46E50092ACC1 isa PBXBuildFile 0EC5D4C21B4C46E50092ACC1 fileRef 0EC5D4B11B4C46E50092ACC1 isa PBXBuildFile 0EC5D4C31B4C46E50092ACC1 fileRef 0EC5D4B21B4C46E50092ACC1 isa PBXBuildFile 0EC5D4C41B4C46E50092ACC1 fileRef 0EC5D4B31B4C46E50092ACC1 isa PBXBuildFile 0EC5D4C51B4C46E50092ACC1 fileRef 0EC5D4B41B4C46E50092ACC1 isa PBXBuildFile 0EC5D4C81B4C480E0092ACC1 isa PBXFileReference lastKnownFileType image.jpeg name video_thumb.jpg path MWPhotoBrowser/Local Media/video_thumb.jpg sourceTree SOURCE_ROOT 0EC5D4C91B4C480E0092ACC1 fileRef 0EC5D4C81B4C480E0092ACC1 isa PBXBuildFile 234A7CACC7671133BED1A091 includeInIndex 1 isa PBXFileReference lastKnownFileType text.xcconfig name Pods-MWPhotoBrowser_Tests.release.xcconfig path Pods/Target Support Files/Pods-MWPhotoBrowser_Tests/Pods-MWPhotoBrowser_Tests.release.xcconfig sourceTree <group> 2BD41C6E3DA363EE7B81636E explicitFileType archive.ar includeInIndex 0 isa PBXFileReference path libPods-MWPhotoBrowser_Tests.a sourceTree BUILT_PRODUCTS_DIR 3CC5A2FF9CBF1B37D7CB6F91 buildActionMask 2147483647 files inputPaths isa PBXShellScriptBuildPhase name Copy Pods Resources outputPaths runOnlyForDeploymentPostprocessing 0 shellPath /bin/sh shellScript "${SRCROOT}/Pods/Target Support Files/Pods-MWPhotoBrowser_Tests/Pods-MWPhotoBrowser_Tests-resources.sh" showEnvVarsInLog 0 42FCB8D314CA85E2DF2C6434 includeInIndex 1 isa PBXFileReference lastKnownFileType text name LICENSE path ../LICENSE sourceTree <group> 4C18E1171B47E72800E3D63A fileEncoding 4 isa PBXFileReference lastKnownFileType sourcecode.c.h path AppDelegate.h sourceTree <group> 4C18E1181B47E72800E3D63A fileEncoding 4 isa PBXFileReference lastKnownFileType sourcecode.c.objc path AppDelegate.m sourceTree <group> 4C18E1191B47E72800E3D63A fileEncoding 4 isa PBXFileReference lastKnownFileType sourcecode.c.h path Menu.h sourceTree <group> 4C18E11A1B47E72800E3D63A fileEncoding 4 isa PBXFileReference lastKnownFileType sourcecode.c.objc path Menu.m sourceTree <group> 4C18E11B1B47E72800E3D63A fileRef 4C18E1181B47E72800E3D63A isa PBXBuildFile 4C18E11C1B47E72800E3D63A fileRef 4C18E11A1B47E72800E3D63A isa PBXBuildFile 4C18E1431B47F05900E3D63A children 0EC5D4C81B4C480E0092ACC1 0EC5D4A41B4C46E50092ACC1 0EC5D4A51B4C46E50092ACC1 0EC5D4A61B4C46E50092ACC1 0EC5D4A71B4C46E50092ACC1 0EC5D4A81B4C46E50092ACC1 0EC5D4A91B4C46E50092ACC1 0EC5D4AA1B4C46E50092ACC1 0EC5D4AB1B4C46E50092ACC1 0EC5D4AC1B4C46E50092ACC1 0EC5D4AD1B4C46E50092ACC1 0EC5D4AE1B4C46E50092ACC1 0EC5D4AF1B4C46E50092ACC1 0EC5D4B01B4C46E50092ACC1 0EC5D4B11B4C46E50092ACC1 0EC5D4B21B4C46E50092ACC1 0EC5D4B31B4C46E50092ACC1 0EC5D4B41B4C46E50092ACC1 isa PBXGroup name Local Media path Photos sourceTree <group> 4D7FBE8AC8249BBC4ADD838E includeInIndex 1 isa PBXFileReference lastKnownFileType text.xcconfig name Pods-MWPhotoBrowser_Example.release.xcconfig path Pods/Target Support Files/Pods-MWPhotoBrowser_Example/Pods-MWPhotoBrowser_Example.release.xcconfig sourceTree <group> 4E0A14EFB687307A0833E979 includeInIndex 1 isa PBXFileReference lastKnownFileType text name MWPhotoBrowser.podspec path ../MWPhotoBrowser.podspec sourceTree <group> 542176705B9DBC22BBCDF3E4 children 6E2C60685D353B336094E4E7 4D7FBE8AC8249BBC4ADD838E CA6EC4575DA50FD5A6F8685B 234A7CACC7671133BED1A091 isa PBXGroup name Pods sourceTree <group> 5FDE805C8BB47D59B5BCB5FA buildActionMask 2147483647 files inputPaths isa PBXShellScriptBuildPhase name Copy Pods Resources outputPaths runOnlyForDeploymentPostprocessing 0 shellPath /bin/sh shellScript "${SRCROOT}/Pods/Target Support Files/Pods-MWPhotoBrowser_Example/Pods-MWPhotoBrowser_Example-resources.sh" showEnvVarsInLog 0 6003F581195388D10070C39A children 4E0A14EFB687307A0833E979 B5C85CB7FE89F584D392C9CF 42FCB8D314CA85E2DF2C6434 6003F593195388D20070C39A 6003F5B5195388D20070C39A 6003F58C195388D20070C39A 6003F58B195388D20070C39A 542176705B9DBC22BBCDF3E4 isa PBXGroup sourceTree <group> 6003F582195388D10070C39A attributes CLASSPREFIX MW LastUpgradeCheck 0730 ORGANIZATIONNAME Michael Waterfall TargetAttributes 6003F5AD195388D20070C39A TestTargetID 6003F589195388D20070C39A buildConfigurationList 6003F585195388D10070C39A compatibilityVersion Xcode 3.2 developmentRegion English hasScannedForEncodings 0 isa PBXProject knownRegions en Base mainGroup 6003F581195388D10070C39A productRefGroup 6003F58B195388D20070C39A projectDirPath projectReferences projectRoot targets 6003F589195388D20070C39A 6003F5AD195388D20070C39A 6003F585195388D10070C39A buildConfigurations 6003F5BD195388D20070C39A 6003F5BE195388D20070C39A defaultConfigurationIsVisible 0 defaultConfigurationName Release isa XCConfigurationList 6003F586195388D20070C39A buildActionMask 2147483647 files 4C18E11B1B47E72800E3D63A 6003F59A195388D20070C39A 4C18E11C1B47E72800E3D63A isa PBXSourcesBuildPhase runOnlyForDeploymentPostprocessing 0 6003F587195388D20070C39A buildActionMask 2147483647 files 6003F590195388D20070C39A 6003F592195388D20070C39A 6003F58E195388D20070C39A BDF7E0625C136532C64B3038 isa PBXFrameworksBuildPhase runOnlyForDeploymentPostprocessing 0 6003F588195388D20070C39A buildActionMask 2147483647 files 0EC5D4B51B4C46E50092ACC1 0EC5D4C31B4C46E50092ACC1 0EC5D4B71B4C46E50092ACC1 873B8AEB1B1F5CCA007FD442 6003F5A9195388D20070C39A 0EC5D4BD1B4C46E50092ACC1 0EC5D4BF1B4C46E50092ACC1 0EC5D4BE1B4C46E50092ACC1 6003F598195388D20070C39A 0EC5D4BB1B4C46E50092ACC1 0EC5D4C41B4C46E50092ACC1 0EC5D4C51B4C46E50092ACC1 0EC5D4C21B4C46E50092ACC1 0EC5D4C01B4C46E50092ACC1 0EC5D4C11B4C46E50092ACC1 0EC5D4B61B4C46E50092ACC1 0EC5D4C91B4C480E0092ACC1 0EC5D4BC1B4C46E50092ACC1 0EC5D4BA1B4C46E50092ACC1 0EC5D4B91B4C46E50092ACC1 0EC5D4B81B4C46E50092ACC1 isa PBXResourcesBuildPhase runOnlyForDeploymentPostprocessing 0 6003F589195388D20070C39A buildConfigurationList 6003F5BF195388D20070C39A buildPhases 81B3EFFED33BEA59184A897C 6003F586195388D20070C39A 6003F587195388D20070C39A 6003F588195388D20070C39A 5FDE805C8BB47D59B5BCB5FA C7E06D0576A92A1C834427BE buildRules dependencies isa PBXNativeTarget name MWPhotoBrowser_Example productName MWPhotoBrowser productReference 6003F58A195388D20070C39A productType com.apple.product-type.application 6003F58A195388D20070C39A explicitFileType wrapper.application includeInIndex 0 isa PBXFileReference path MWPhotoBrowser_Example.app sourceTree BUILT_PRODUCTS_DIR 6003F58B195388D20070C39A children 6003F58A195388D20070C39A 6003F5AE195388D20070C39A isa PBXGroup name Products sourceTree <group> 6003F58C195388D20070C39A children 6003F58D195388D20070C39A 6003F58F195388D20070C39A 6003F591195388D20070C39A 6003F5AF195388D20070C39A 9C165E5EE0D40A4673EB6CB0 2BD41C6E3DA363EE7B81636E isa PBXGroup name Frameworks sourceTree <group> 6003F58D195388D20070C39A isa PBXFileReference lastKnownFileType wrapper.framework name Foundation.framework path System/Library/Frameworks/Foundation.framework sourceTree SDKROOT 6003F58E195388D20070C39A fileRef 6003F58D195388D20070C39A isa PBXBuildFile 6003F58F195388D20070C39A isa PBXFileReference lastKnownFileType wrapper.framework name CoreGraphics.framework path System/Library/Frameworks/CoreGraphics.framework sourceTree SDKROOT 6003F590195388D20070C39A fileRef 6003F58F195388D20070C39A isa PBXBuildFile 6003F591195388D20070C39A isa PBXFileReference lastKnownFileType wrapper.framework name UIKit.framework path System/Library/Frameworks/UIKit.framework sourceTree SDKROOT 6003F592195388D20070C39A fileRef 6003F591195388D20070C39A isa PBXBuildFile 6003F593195388D20070C39A children 4C18E1171B47E72800E3D63A 4C18E1181B47E72800E3D63A 4C18E1191B47E72800E3D63A 4C18E11A1B47E72800E3D63A 873B8AEA1B1F5CCA007FD442 6003F5A8195388D20070C39A 4C18E1431B47F05900E3D63A 6003F594195388D20070C39A isa PBXGroup name Example path MWPhotoBrowser sourceTree <group> 6003F594195388D20070C39A children 6003F595195388D20070C39A 6003F596195388D20070C39A 6003F599195388D20070C39A 6003F59B195388D20070C39A isa PBXGroup name Supporting Files sourceTree <group> 6003F595195388D20070C39A isa PBXFileReference lastKnownFileType text.plist.xml path MWPhotoBrowser-Info.plist sourceTree <group> 6003F596195388D20070C39A children 6003F597195388D20070C39A isa PBXVariantGroup name InfoPlist.strings sourceTree <group> 6003F597195388D20070C39A isa PBXFileReference lastKnownFileType text.plist.strings name en path en.lproj/InfoPlist.strings sourceTree <group> 6003F598195388D20070C39A fileRef 6003F596195388D20070C39A isa PBXBuildFile 6003F599195388D20070C39A isa PBXFileReference lastKnownFileType sourcecode.c.objc path main.m sourceTree <group> 6003F59A195388D20070C39A fileRef 6003F599195388D20070C39A isa PBXBuildFile 6003F59B195388D20070C39A isa PBXFileReference lastKnownFileType sourcecode.c.h path MWPhotoBrowser-Prefix.pch sourceTree <group> 6003F5A8195388D20070C39A isa PBXFileReference lastKnownFileType folder.assetcatalog path Images.xcassets sourceTree <group> 6003F5A9195388D20070C39A fileRef 6003F5A8195388D20070C39A isa PBXBuildFile 6003F5AA195388D20070C39A buildActionMask 2147483647 files 6003F5BC195388D20070C39A isa PBXSourcesBuildPhase runOnlyForDeploymentPostprocessing 0 6003F5AB195388D20070C39A buildActionMask 2147483647 files 6003F5B0195388D20070C39A 6003F5B2195388D20070C39A 6003F5B1195388D20070C39A BBD9ABBBC6D859245030E9BA isa PBXFrameworksBuildPhase runOnlyForDeploymentPostprocessing 0 6003F5AC195388D20070C39A buildActionMask 2147483647 files 6003F5BA195388D20070C39A isa PBXResourcesBuildPhase runOnlyForDeploymentPostprocessing 0 6003F5AD195388D20070C39A buildConfigurationList 6003F5C2195388D20070C39A buildPhases F7D70421FEADB9922E5F26C7 6003F5AA195388D20070C39A 6003F5AB195388D20070C39A 6003F5AC195388D20070C39A 3CC5A2FF9CBF1B37D7CB6F91 02040E41C46EB311FEA82F90 buildRules dependencies 6003F5B4195388D20070C39A isa PBXNativeTarget name MWPhotoBrowser_Tests productName MWPhotoBrowserTests productReference 6003F5AE195388D20070C39A productType com.apple.product-type.bundle.unit-test 6003F5AE195388D20070C39A explicitFileType wrapper.cfbundle includeInIndex 0 isa PBXFileReference path MWPhotoBrowser_Tests.xctest sourceTree BUILT_PRODUCTS_DIR 6003F5AF195388D20070C39A isa PBXFileReference lastKnownFileType wrapper.framework name XCTest.framework path Library/Frameworks/XCTest.framework sourceTree DEVELOPER_DIR 6003F5B0195388D20070C39A fileRef 6003F5AF195388D20070C39A isa PBXBuildFile 6003F5B1195388D20070C39A fileRef 6003F58D195388D20070C39A isa PBXBuildFile 6003F5B2195388D20070C39A fileRef 6003F591195388D20070C39A isa PBXBuildFile 6003F5B3195388D20070C39A containerPortal 6003F582195388D10070C39A isa PBXContainerItemProxy proxyType 1 remoteGlobalIDString 6003F589195388D20070C39A remoteInfo MWPhotoBrowser 6003F5B4195388D20070C39A isa PBXTargetDependency target 6003F589195388D20070C39A targetProxy 6003F5B3195388D20070C39A 6003F5B5195388D20070C39A children 6003F5BB195388D20070C39A 6003F5B6195388D20070C39A isa PBXGroup path Tests sourceTree <group> 6003F5B6195388D20070C39A children 6003F5B7195388D20070C39A 6003F5B8195388D20070C39A 606FC2411953D9B200FFA9A0 isa PBXGroup name Supporting Files sourceTree <group> 6003F5B7195388D20070C39A isa PBXFileReference lastKnownFileType text.plist.xml path Tests-Info.plist sourceTree <group> 6003F5B8195388D20070C39A children 6003F5B9195388D20070C39A isa PBXVariantGroup name InfoPlist.strings sourceTree <group> 6003F5B9195388D20070C39A isa PBXFileReference lastKnownFileType text.plist.strings name en path en.lproj/InfoPlist.strings sourceTree <group> 6003F5BA195388D20070C39A fileRef 6003F5B8195388D20070C39A isa PBXBuildFile 6003F5BB195388D20070C39A isa PBXFileReference lastKnownFileType sourcecode.c.objc path Tests.m sourceTree <group> 6003F5BC195388D20070C39A fileRef 6003F5BB195388D20070C39A isa PBXBuildFile 6003F5BD195388D20070C39A buildSettings ALWAYS_SEARCH_USER_PATHS NO CLANG_CXX_LANGUAGE_STANDARD gnu++0x CLANG_CXX_LIBRARY libc++ CLANG_ENABLE_MODULES YES CLANG_ENABLE_OBJC_ARC YES CLANG_WARN_BOOL_CONVERSION YES CLANG_WARN_CONSTANT_CONVERSION YES CLANG_WARN_DIRECT_OBJC_ISA_USAGE YES_ERROR CLANG_WARN_EMPTY_BODY YES CLANG_WARN_ENUM_CONVERSION YES CLANG_WARN_INT_CONVERSION YES CLANG_WARN_OBJC_ROOT_CLASS YES_ERROR CLANG_WARN__DUPLICATE_METHOD_MATCH YES CODE_SIGN_IDENTITY[sdk=iphoneos*] iPhone Developer COPY_PHASE_STRIP NO ENABLE_TESTABILITY YES GCC_C_LANGUAGE_STANDARD gnu99 GCC_DYNAMIC_NO_PIC NO GCC_OPTIMIZATION_LEVEL 0 GCC_PREPROCESSOR_DEFINITIONS DEBUG=1 $(inherited) GCC_SYMBOLS_PRIVATE_EXTERN NO GCC_WARN_64_TO_32_BIT_CONVERSION YES GCC_WARN_ABOUT_RETURN_TYPE YES_ERROR GCC_WARN_UNDECLARED_SELECTOR YES GCC_WARN_UNINITIALIZED_AUTOS YES_AGGRESSIVE GCC_WARN_UNUSED_FUNCTION YES GCC_WARN_UNUSED_VARIABLE YES IPHONEOS_DEPLOYMENT_TARGET 7.1 ONLY_ACTIVE_ARCH YES SDKROOT iphoneos TARGETED_DEVICE_FAMILY 1,2 isa XCBuildConfiguration name Debug 6003F5BE195388D20070C39A buildSettings ALWAYS_SEARCH_USER_PATHS NO CLANG_CXX_LANGUAGE_STANDARD gnu++0x CLANG_CXX_LIBRARY libc++ CLANG_ENABLE_MODULES YES CLANG_ENABLE_OBJC_ARC YES CLANG_WARN_BOOL_CONVERSION YES CLANG_WARN_CONSTANT_CONVERSION YES CLANG_WARN_DIRECT_OBJC_ISA_USAGE YES_ERROR CLANG_WARN_EMPTY_BODY YES CLANG_WARN_ENUM_CONVERSION YES CLANG_WARN_INT_CONVERSION YES CLANG_WARN_OBJC_ROOT_CLASS YES_ERROR CLANG_WARN__DUPLICATE_METHOD_MATCH YES CODE_SIGN_IDENTITY[sdk=iphoneos*] iPhone Developer COPY_PHASE_STRIP YES ENABLE_NS_ASSERTIONS NO GCC_C_LANGUAGE_STANDARD gnu99 GCC_WARN_64_TO_32_BIT_CONVERSION YES GCC_WARN_ABOUT_RETURN_TYPE YES_ERROR GCC_WARN_UNDECLARED_SELECTOR YES GCC_WARN_UNINITIALIZED_AUTOS YES_AGGRESSIVE GCC_WARN_UNUSED_FUNCTION YES GCC_WARN_UNUSED_VARIABLE YES IPHONEOS_DEPLOYMENT_TARGET 7.1 SDKROOT iphoneos TARGETED_DEVICE_FAMILY 1,2 VALIDATE_PRODUCT YES isa XCBuildConfiguration name Release 6003F5BF195388D20070C39A buildConfigurations 6003F5C0195388D20070C39A 6003F5C1195388D20070C39A defaultConfigurationIsVisible 0 defaultConfigurationName Release isa XCConfigurationList 6003F5C0195388D20070C39A baseConfigurationReference 6E2C60685D353B336094E4E7 buildSettings ASSETCATALOG_COMPILER_APPICON_NAME AppIcon ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME LaunchImage GCC_PRECOMPILE_PREFIX_HEADER YES GCC_PREFIX_HEADER MWPhotoBrowser/MWPhotoBrowser-Prefix.pch INFOPLIST_FILE MWPhotoBrowser/MWPhotoBrowser-Info.plist IPHONEOS_DEPLOYMENT_TARGET 7.0 MODULE_NAME ExampleApp PRODUCT_BUNDLE_IDENTIFIER org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier} PRODUCT_NAME $(TARGET_NAME) WRAPPER_EXTENSION app isa XCBuildConfiguration name Debug 6003F5C1195388D20070C39A baseConfigurationReference 4D7FBE8AC8249BBC4ADD838E buildSettings ASSETCATALOG_COMPILER_APPICON_NAME AppIcon ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME LaunchImage GCC_PRECOMPILE_PREFIX_HEADER YES GCC_PREFIX_HEADER MWPhotoBrowser/MWPhotoBrowser-Prefix.pch INFOPLIST_FILE MWPhotoBrowser/MWPhotoBrowser-Info.plist IPHONEOS_DEPLOYMENT_TARGET 7.0 MODULE_NAME ExampleApp PRODUCT_BUNDLE_IDENTIFIER org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier} PRODUCT_NAME $(TARGET_NAME) WRAPPER_EXTENSION app isa XCBuildConfiguration name Release 6003F5C2195388D20070C39A buildConfigurations 6003F5C3195388D20070C39A 6003F5C4195388D20070C39A defaultConfigurationIsVisible 0 defaultConfigurationName Release isa XCConfigurationList 6003F5C3195388D20070C39A baseConfigurationReference CA6EC4575DA50FD5A6F8685B buildSettings BUNDLE_LOADER $(TEST_HOST) FRAMEWORK_SEARCH_PATHS $(SDKROOT)/Developer/Library/Frameworks $(inherited) $(DEVELOPER_FRAMEWORKS_DIR) GCC_PRECOMPILE_PREFIX_HEADER YES GCC_PREFIX_HEADER Tests/Tests-Prefix.pch GCC_PREPROCESSOR_DEFINITIONS DEBUG=1 $(inherited) INFOPLIST_FILE Tests/Tests-Info.plist PRODUCT_BUNDLE_IDENTIFIER org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier} PRODUCT_NAME $(TARGET_NAME) TEST_HOST $(BUILT_PRODUCTS_DIR)/MWPhotoBrowser_Example.app/MWPhotoBrowser_Example WRAPPER_EXTENSION xctest isa XCBuildConfiguration name Debug 6003F5C4195388D20070C39A baseConfigurationReference 234A7CACC7671133BED1A091 buildSettings BUNDLE_LOADER $(TEST_HOST) FRAMEWORK_SEARCH_PATHS $(SDKROOT)/Developer/Library/Frameworks $(inherited) $(DEVELOPER_FRAMEWORKS_DIR) GCC_PRECOMPILE_PREFIX_HEADER YES GCC_PREFIX_HEADER Tests/Tests-Prefix.pch INFOPLIST_FILE Tests/Tests-Info.plist PRODUCT_BUNDLE_IDENTIFIER org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier} PRODUCT_NAME $(TARGET_NAME) TEST_HOST $(BUILT_PRODUCTS_DIR)/MWPhotoBrowser_Example.app/MWPhotoBrowser_Example WRAPPER_EXTENSION xctest isa XCBuildConfiguration name Release 606FC2411953D9B200FFA9A0 isa PBXFileReference lastKnownFileType sourcecode.c.h path Tests-Prefix.pch sourceTree <group> 6E2C60685D353B336094E4E7 includeInIndex 1 isa PBXFileReference lastKnownFileType text.xcconfig name Pods-MWPhotoBrowser_Example.debug.xcconfig path Pods/Target Support Files/Pods-MWPhotoBrowser_Example/Pods-MWPhotoBrowser_Example.debug.xcconfig sourceTree <group> 81B3EFFED33BEA59184A897C buildActionMask 2147483647 files inputPaths isa PBXShellScriptBuildPhase name Check Pods Manifest.lock outputPaths runOnlyForDeploymentPostprocessing 0 shellPath /bin/sh shellScript diff "${PODS_ROOT}/../Podfile.lock" "${PODS_ROOT}/Manifest.lock" > /dev/null if [[ $? != 0 ]] ; then cat << EOM error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation. EOM exit 1 fi showEnvVarsInLog 0 873B8AEA1B1F5CCA007FD442 fileEncoding 4 isa PBXFileReference lastKnownFileType file.storyboard path Main.storyboard sourceTree <group> 873B8AEB1B1F5CCA007FD442 fileRef 873B8AEA1B1F5CCA007FD442 isa PBXBuildFile 9C165E5EE0D40A4673EB6CB0 explicitFileType archive.ar includeInIndex 0 isa PBXFileReference path libPods-MWPhotoBrowser_Example.a sourceTree BUILT_PRODUCTS_DIR B5C85CB7FE89F584D392C9CF includeInIndex 1 isa PBXFileReference lastKnownFileType net.daringfireball.markdown name README.md path ../README.md sourceTree <group> BBD9ABBBC6D859245030E9BA fileRef 2BD41C6E3DA363EE7B81636E isa PBXBuildFile BDF7E0625C136532C64B3038 fileRef 9C165E5EE0D40A4673EB6CB0 isa PBXBuildFile C7E06D0576A92A1C834427BE buildActionMask 2147483647 files inputPaths isa PBXShellScriptBuildPhase name Embed Pods Frameworks outputPaths runOnlyForDeploymentPostprocessing 0 shellPath /bin/sh shellScript "${SRCROOT}/Pods/Target Support Files/Pods-MWPhotoBrowser_Example/Pods-MWPhotoBrowser_Example-frameworks.sh" showEnvVarsInLog 0 CA6EC4575DA50FD5A6F8685B includeInIndex 1 isa PBXFileReference lastKnownFileType text.xcconfig name Pods-MWPhotoBrowser_Tests.debug.xcconfig path Pods/Target Support Files/Pods-MWPhotoBrowser_Tests/Pods-MWPhotoBrowser_Tests.debug.xcconfig sourceTree <group> F7D70421FEADB9922E5F26C7 buildActionMask 2147483647 files inputPaths isa PBXShellScriptBuildPhase name Check Pods Manifest.lock outputPaths runOnlyForDeploymentPostprocessing 0 shellPath /bin/sh shellScript diff "${PODS_ROOT}/../Podfile.lock" "${PODS_ROOT}/Manifest.lock" > /dev/null if [[ $? != 0 ]] ; then cat << EOM error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation. EOM exit 1 fi showEnvVarsInLog 0 rootObject 6003F582195388D10070C39A ================================================ FILE: Example/MWPhotoBrowser.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Example/MWPhotoBrowser.xcodeproj/xcshareddata/xcschemes/MWPhotoBrowser-Example.xcscheme ================================================ ================================================ FILE: Example/MWPhotoBrowser.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Example/Podfile ================================================ source 'https://github.com/CocoaPods/Specs.git' target 'MWPhotoBrowser_Example', :exclusive => true do pod "MWPhotoBrowser", :path => "../" end target 'MWPhotoBrowser_Tests', :exclusive => true do pod "MWPhotoBrowser", :path => "../" pod 'Specta' pod 'Expecta' pod 'FBSnapshotTestCase' pod 'Expecta+Snapshots' end ================================================ FILE: Example/Pods/DACircularProgress/DACircularProgress/DACircularProgressView.h ================================================ // // DACircularProgressView.h // DACircularProgress // // Created by Daniel Amitay on 2/6/12. // Copyright (c) 2012 Daniel Amitay. All rights reserved. // #import @interface DACircularProgressView : UIView @property(nonatomic, strong) UIColor *trackTintColor UI_APPEARANCE_SELECTOR; @property(nonatomic, strong) UIColor *progressTintColor UI_APPEARANCE_SELECTOR; @property(nonatomic, strong) UIColor *innerTintColor UI_APPEARANCE_SELECTOR; @property(nonatomic) NSInteger roundedCorners UI_APPEARANCE_SELECTOR; // Can not use BOOL with UI_APPEARANCE_SELECTOR :-( @property(nonatomic) CGFloat thicknessRatio UI_APPEARANCE_SELECTOR; @property(nonatomic) NSInteger clockwiseProgress UI_APPEARANCE_SELECTOR; // Can not use BOOL with UI_APPEARANCE_SELECTOR :-( @property(nonatomic) CGFloat progress; @property(nonatomic) CGFloat indeterminateDuration UI_APPEARANCE_SELECTOR; @property(nonatomic) NSInteger indeterminate UI_APPEARANCE_SELECTOR; // Can not use BOOL with UI_APPEARANCE_SELECTOR :-( - (void)setProgress:(CGFloat)progress animated:(BOOL)animated; - (void)setProgress:(CGFloat)progress animated:(BOOL)animated initialDelay:(CFTimeInterval)initialDelay; - (void)setProgress:(CGFloat)progress animated:(BOOL)animated initialDelay:(CFTimeInterval)initialDelay withDuration:(CFTimeInterval)duration; @end ================================================ FILE: Example/Pods/DACircularProgress/DACircularProgress/DACircularProgressView.m ================================================ // // DACircularProgressView.m // DACircularProgress // // Created by Daniel Amitay on 2/6/12. // Copyright (c) 2012 Daniel Amitay. All rights reserved. // #import "DACircularProgressView.h" #import @interface DACircularProgressLayer : CALayer @property(nonatomic, strong) UIColor *trackTintColor; @property(nonatomic, strong) UIColor *progressTintColor; @property(nonatomic, strong) UIColor *innerTintColor; @property(nonatomic) NSInteger roundedCorners; @property(nonatomic) CGFloat thicknessRatio; @property(nonatomic) CGFloat progress; @property(nonatomic) NSInteger clockwiseProgress; @end @implementation DACircularProgressLayer @dynamic trackTintColor; @dynamic progressTintColor; @dynamic innerTintColor; @dynamic roundedCorners; @dynamic thicknessRatio; @dynamic progress; @dynamic clockwiseProgress; + (BOOL)needsDisplayForKey:(NSString *)key { if ([key isEqualToString:@"progress"]) { return YES; } else { return [super needsDisplayForKey:key]; } } - (void)drawInContext:(CGContextRef)context { CGRect rect = self.bounds; CGPoint centerPoint = CGPointMake(rect.size.width / 2.0f, rect.size.height / 2.0f); CGFloat radius = MIN(rect.size.height, rect.size.width) / 2.0f; BOOL clockwise = (self.clockwiseProgress != 0); CGFloat progress = MIN(self.progress, 1.0f - FLT_EPSILON); CGFloat radians = 0; if (clockwise) { radians = (float)((progress * 2.0f * M_PI) - M_PI_2); } else { radians = (float)(3 * M_PI_2 - (progress * 2.0f * M_PI)); } CGContextSetFillColorWithColor(context, self.trackTintColor.CGColor); CGMutablePathRef trackPath = CGPathCreateMutable(); CGPathMoveToPoint(trackPath, NULL, centerPoint.x, centerPoint.y); CGPathAddArc(trackPath, NULL, centerPoint.x, centerPoint.y, radius, (float)(2.0f * M_PI), 0.0f, TRUE); CGPathCloseSubpath(trackPath); CGContextAddPath(context, trackPath); CGContextFillPath(context); CGPathRelease(trackPath); if (progress > 0.0f) { CGContextSetFillColorWithColor(context, self.progressTintColor.CGColor); CGMutablePathRef progressPath = CGPathCreateMutable(); CGPathMoveToPoint(progressPath, NULL, centerPoint.x, centerPoint.y); CGPathAddArc(progressPath, NULL, centerPoint.x, centerPoint.y, radius, (float)(3.0f * M_PI_2), radians, !clockwise); CGPathCloseSubpath(progressPath); CGContextAddPath(context, progressPath); CGContextFillPath(context); CGPathRelease(progressPath); } if (progress > 0.0f && self.roundedCorners) { CGFloat pathWidth = radius * self.thicknessRatio; CGFloat xOffset = radius * (1.0f + ((1.0f - (self.thicknessRatio / 2.0f)) * cosf(radians))); CGFloat yOffset = radius * (1.0f + ((1.0f - (self.thicknessRatio / 2.0f)) * sinf(radians))); CGPoint endPoint = CGPointMake(xOffset, yOffset); CGRect startEllipseRect = (CGRect) { .origin.x = centerPoint.x - pathWidth / 2.0f, .origin.y = 0.0f, .size.width = pathWidth, .size.height = pathWidth }; CGContextAddEllipseInRect(context, startEllipseRect); CGContextFillPath(context); CGRect endEllipseRect = (CGRect) { .origin.x = endPoint.x - pathWidth / 2.0f, .origin.y = endPoint.y - pathWidth / 2.0f, .size.width = pathWidth, .size.height = pathWidth }; CGContextAddEllipseInRect(context, endEllipseRect); CGContextFillPath(context); } CGContextSetBlendMode(context, kCGBlendModeClear); CGFloat innerRadius = radius * (1.0f - self.thicknessRatio); CGRect clearRect = (CGRect) { .origin.x = centerPoint.x - innerRadius, .origin.y = centerPoint.y - innerRadius, .size.width = innerRadius * 2.0f, .size.height = innerRadius * 2.0f }; CGContextAddEllipseInRect(context, clearRect); CGContextFillPath(context); if (self.innerTintColor) { CGContextSetBlendMode(context, kCGBlendModeNormal); CGContextSetFillColorWithColor(context, [self.innerTintColor CGColor]); CGContextAddEllipseInRect(context, clearRect); CGContextFillPath(context); } } @end @interface DACircularProgressView () @end @implementation DACircularProgressView + (void) initialize { if (self == [DACircularProgressView class]) { DACircularProgressView *circularProgressViewAppearance = [DACircularProgressView appearance]; [circularProgressViewAppearance setTrackTintColor:[[UIColor whiteColor] colorWithAlphaComponent:0.3f]]; [circularProgressViewAppearance setProgressTintColor:[UIColor whiteColor]]; [circularProgressViewAppearance setInnerTintColor:nil]; [circularProgressViewAppearance setBackgroundColor:[UIColor clearColor]]; [circularProgressViewAppearance setThicknessRatio:0.3f]; [circularProgressViewAppearance setRoundedCorners:NO]; [circularProgressViewAppearance setClockwiseProgress:YES]; [circularProgressViewAppearance setIndeterminateDuration:2.0f]; [circularProgressViewAppearance setIndeterminate:NO]; } } + (Class)layerClass { return [DACircularProgressLayer class]; } - (DACircularProgressLayer *)circularProgressLayer { return (DACircularProgressLayer *)self.layer; } - (id)init { return [super initWithFrame:CGRectMake(0.0f, 0.0f, 40.0f, 40.0f)]; } - (void)didMoveToWindow { CGFloat windowContentsScale = self.window.screen.scale; self.circularProgressLayer.contentsScale = windowContentsScale; [self.circularProgressLayer setNeedsDisplay]; } #pragma mark - Progress - (CGFloat)progress { return self.circularProgressLayer.progress; } - (void)setProgress:(CGFloat)progress { [self setProgress:progress animated:NO]; } - (void)setProgress:(CGFloat)progress animated:(BOOL)animated { [self setProgress:progress animated:animated initialDelay:0.0]; } - (void)setProgress:(CGFloat)progress animated:(BOOL)animated initialDelay:(CFTimeInterval)initialDelay { CGFloat pinnedProgress = MIN(MAX(progress, 0.0f), 1.0f); [self setProgress:progress animated:animated initialDelay:initialDelay withDuration:fabs(self.progress - pinnedProgress)]; } - (void)setProgress:(CGFloat)progress animated:(BOOL)animated initialDelay:(CFTimeInterval)initialDelay withDuration:(CFTimeInterval)duration { [self.layer removeAnimationForKey:@"indeterminateAnimation"]; [self.circularProgressLayer removeAnimationForKey:@"progress"]; CGFloat pinnedProgress = MIN(MAX(progress, 0.0f), 1.0f); if (animated) { CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"progress"]; animation.duration = duration; animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; animation.fillMode = kCAFillModeForwards; animation.fromValue = [NSNumber numberWithFloat:self.progress]; animation.toValue = [NSNumber numberWithFloat:pinnedProgress]; animation.beginTime = CACurrentMediaTime() + initialDelay; animation.delegate = self; [self.circularProgressLayer addAnimation:animation forKey:@"progress"]; } else { [self.circularProgressLayer setNeedsDisplay]; self.circularProgressLayer.progress = pinnedProgress; } } - (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)flag { NSNumber *pinnedProgressNumber = [animation valueForKey:@"toValue"]; self.circularProgressLayer.progress = [pinnedProgressNumber floatValue]; } #pragma mark - UIAppearance methods - (UIColor *)trackTintColor { return self.circularProgressLayer.trackTintColor; } - (void)setTrackTintColor:(UIColor *)trackTintColor { self.circularProgressLayer.trackTintColor = trackTintColor; [self.circularProgressLayer setNeedsDisplay]; } - (UIColor *)progressTintColor { return self.circularProgressLayer.progressTintColor; } - (void)setProgressTintColor:(UIColor *)progressTintColor { self.circularProgressLayer.progressTintColor = progressTintColor; [self.circularProgressLayer setNeedsDisplay]; } - (UIColor *)innerTintColor { return self.circularProgressLayer.innerTintColor; } - (void)setInnerTintColor:(UIColor *)innerTintColor { self.circularProgressLayer.innerTintColor = innerTintColor; [self.circularProgressLayer setNeedsDisplay]; } - (NSInteger)roundedCorners { return self.roundedCorners; } - (void)setRoundedCorners:(NSInteger)roundedCorners { self.circularProgressLayer.roundedCorners = roundedCorners; [self.circularProgressLayer setNeedsDisplay]; } - (CGFloat)thicknessRatio { return self.circularProgressLayer.thicknessRatio; } - (void)setThicknessRatio:(CGFloat)thicknessRatio { self.circularProgressLayer.thicknessRatio = MIN(MAX(thicknessRatio, 0.f), 1.f); [self.circularProgressLayer setNeedsDisplay]; } - (NSInteger)indeterminate { CAAnimation *spinAnimation = [self.layer animationForKey:@"indeterminateAnimation"]; return (spinAnimation == nil ? 0 : 1); } - (void)setIndeterminate:(NSInteger)indeterminate { if (indeterminate) { if (!self.indeterminate) { CABasicAnimation *spinAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"]; spinAnimation.byValue = [NSNumber numberWithDouble:indeterminate > 0 ? 2.0f*M_PI : -2.0f*M_PI]; spinAnimation.duration = self.indeterminateDuration; spinAnimation.repeatCount = HUGE_VALF; [self.layer addAnimation:spinAnimation forKey:@"indeterminateAnimation"]; } } else { [self.layer removeAnimationForKey:@"indeterminateAnimation"]; } } - (NSInteger)clockwiseProgress { return self.circularProgressLayer.clockwiseProgress; } - (void)setClockwiseProgress:(NSInteger)clockwiseProgres { self.circularProgressLayer.clockwiseProgress = clockwiseProgres; [self.circularProgressLayer setNeedsDisplay]; } @end ================================================ FILE: Example/Pods/DACircularProgress/DACircularProgress/DALabeledCircularProgressView.h ================================================ // // DALabeledCircularProgressView.h // DACircularProgressExample // // Created by Josh Sklar on 4/8/14. // Copyright (c) 2014 Shout Messenger. All rights reserved. // #import "DACircularProgressView.h" /** @class DALabeledCircularProgressView @brief Subclass of DACircularProgressView that adds a UILabel. */ @interface DALabeledCircularProgressView : DACircularProgressView /** UILabel placed right on the DACircularProgressView. */ @property (strong, nonatomic) UILabel *progressLabel; @end ================================================ FILE: Example/Pods/DACircularProgress/DACircularProgress/DALabeledCircularProgressView.m ================================================ // // DALabeledCircularProgressView.m // DACircularProgressExample // // Created by Josh Sklar on 4/8/14. // Copyright (c) 2014 Shout Messenger. All rights reserved. // #import "DALabeledCircularProgressView.h" @implementation DALabeledCircularProgressView - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self initializeLabel]; } return self; } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { [self initializeLabel]; } return self; } #pragma mark - Internal methods /** Creates and initializes -[DALabeledCircularProgressView progressLabel]. */ - (void)initializeLabel { self.progressLabel = [[UILabel alloc] initWithFrame:self.bounds]; self.progressLabel.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; self.progressLabel.textAlignment = NSTextAlignmentCenter; self.progressLabel.backgroundColor = [UIColor clearColor]; [self addSubview:self.progressLabel]; } @end ================================================ FILE: Example/Pods/DACircularProgress/LICENSE.md ================================================ # License ## MIT License Copyright (c) 2013 Daniel Amitay (http://danielamitay.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Example/Pods/DACircularProgress/README.md ================================================ ## DACircularProgress `DACircularProgress` is a `UIView` subclass with circular `UIProgressView` properties. It was originally built to be an imitation of Facebook's photo progress indicator. View the included example project for a demonstration. ![Screenshot](https://github.com/danielamitay/DACircularProgress/raw/master/screenshot.png) ## Installation To use `DACircularProgress`: - Copy over the `DACircularProgress` folder to your project folder. - Make sure that your project includes ``. - `#import "DACircularProgressView.h"` ### Example Code ```objective-c self.progressView = [[DACircularProgressView alloc] initWithFrame:CGRectMake(140.0f, 30.0f, 40.0f, 40.0f)]; self.progressView.roundedCorners = YES; self.progressView.trackTintColor = [UIColor clearColor]; [self.view addSubview:self.progressView]; ``` - You can also use Interface Builder by adding a `UIView` element and setting its class to `DACircularProgress` ## Notes ### Compatibility iOS5.0+ ### Automatic Reference Counting (ARC) support `DACircularProgress` was made with ARC enabled by default. ## Contact - [Personal website](http://danielamitay.com) - [GitHub](http://github.com/danielamitay) - [Twitter](http://twitter.com/danielamitay) - [LinkedIn](http://www.linkedin.com/in/danielamitay) - [Email](hello@danielamitay.com) If you use/enjoy `DACircularProgress`, let me know! ## Credits `DACircularProgress` is brought to you by [Daniel Amitay](http://www.amitay.us) and [contributors to the project](https://github.com/danielamitay/DACircularProgress/contributors). A special thanks to [Cédric Luthi](https://github.com/0xced) for a significant amount of changes. If you have feature suggestions or bug reports, feel free to help out by sending pull requests or by [creating new issues](https://github.com/danielamitay/DACircularProgress/issues/new). ## License ### MIT License Copyright (c) 2013 Daniel Amitay (http://danielamitay.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Example/Pods/Expecta/Expecta/EXPBlockDefinedMatcher.h ================================================ // // EXPRuntimeMatcher.h // Expecta // // Created by Luke Redpath on 26/03/2012. // Copyright (c) 2012 Peter Jihoon Kim. All rights reserved. // #import #import "EXPMatcher.h" #import "EXPDefines.h" @interface EXPBlockDefinedMatcher : NSObject { EXPBoolBlock prerequisiteBlock; EXPBoolBlock matchBlock; EXPStringBlock failureMessageForToBlock; EXPStringBlock failureMessageForNotToBlock; } @property(nonatomic, copy) EXPBoolBlock prerequisiteBlock; @property(nonatomic, copy) EXPBoolBlock matchBlock; @property(nonatomic, copy) EXPStringBlock failureMessageForToBlock; @property(nonatomic, copy) EXPStringBlock failureMessageForNotToBlock; @end ================================================ FILE: Example/Pods/Expecta/Expecta/EXPBlockDefinedMatcher.m ================================================ // // EXPRuntimeMatcher.m // Expecta // // Created by Luke Redpath on 26/03/2012. // Copyright (c) 2012 Peter Jihoon Kim. All rights reserved. // #import "EXPBlockDefinedMatcher.h" @implementation EXPBlockDefinedMatcher - (void)dealloc { self.prerequisiteBlock = nil; self.matchBlock = nil; self.failureMessageForToBlock = nil; self.failureMessageForNotToBlock = nil; [super dealloc]; } @synthesize prerequisiteBlock; @synthesize matchBlock; @synthesize failureMessageForToBlock; @synthesize failureMessageForNotToBlock; - (BOOL)meetsPrerequesiteFor:(id)actual { if (self.prerequisiteBlock) { return self.prerequisiteBlock(); } return YES; } - (BOOL)matches:(id)actual { if (self.matchBlock) { return self.matchBlock(); } return YES; } - (NSString *)failureMessageForTo:(id)actual { if (self.failureMessageForToBlock) { return self.failureMessageForToBlock(); } return nil; } - (NSString *)failureMessageForNotTo:(id)actual { if (self.failureMessageForNotToBlock) { return self.failureMessageForNotToBlock(); } return nil; } @end ================================================ FILE: Example/Pods/Expecta/Expecta/EXPDefines.h ================================================ // // EXPDefines.h // Expecta // // Created by Luke Redpath on 26/03/2012. // Copyright (c) 2012 Peter Jihoon Kim. All rights reserved. // #ifndef Expecta_EXPDefines_h #define Expecta_EXPDefines_h typedef void (^EXPBasicBlock)(); typedef id (^EXPIdBlock)(); typedef BOOL (^EXPBoolBlock)(); typedef NSString *(^EXPStringBlock)(); #endif ================================================ FILE: Example/Pods/Expecta/Expecta/EXPDoubleTuple.h ================================================ #import @interface EXPDoubleTuple : NSObject { double *_values; size_t _size; } @property (nonatomic, assign) double *values; @property (nonatomic, assign) size_t size; - (instancetype)initWithDoubleValues:(double *)values size:(size_t)size NS_DESIGNATED_INITIALIZER; @end ================================================ FILE: Example/Pods/Expecta/Expecta/EXPDoubleTuple.m ================================================ #import "EXPDoubleTuple.h" @implementation EXPDoubleTuple @synthesize values = _values, size = _size; - (instancetype)initWithDoubleValues:(double *)values size:(size_t)size { if ((self = [super init])) { self.values = malloc(sizeof(double) * size); memcpy(self.values, values, sizeof(double) * size); self.size = size; } return self; } - (void)dealloc { free(self.values); [super dealloc]; } - (BOOL)isEqual:(id)object { if (![object isKindOfClass:[EXPDoubleTuple class]]) return NO; EXPDoubleTuple *other = (EXPDoubleTuple *)object; if (self.size == other.size) { for (int i = 0; i < self.size; ++i) { if (self.values[i] != other.values[i]) return NO; } return YES; } return NO; } - (NSString *)description { if (self.size == 2) { return [NSString stringWithFormat:@"Double tuple: {%f, %f}", self.values[0], self.values[1]]; } else if (self.size == 4) { return [NSString stringWithFormat:@"Double tuple: {%f, %f, %f, %f}", self.values[0], self.values[1], self.values[2], self.values[3]]; } return [NSString stringWithFormat:@"Double tuple of unexpected size %zd, sadly", self.size]; } @end ================================================ FILE: Example/Pods/Expecta/Expecta/EXPExpect.h ================================================ #import #import "EXPMatcher.h" #import "EXPDefines.h" @interface EXPExpect : NSObject { EXPIdBlock _actualBlock; id _testCase; int _lineNumber; char *_fileName; BOOL _negative; BOOL _asynchronous; NSTimeInterval _timeout; } @property(nonatomic, copy) EXPIdBlock actualBlock; @property(nonatomic, readonly) id actual; @property(nonatomic, assign) id testCase; @property(nonatomic) int lineNumber; @property(nonatomic) const char *fileName; @property(nonatomic) BOOL negative; @property(nonatomic) BOOL asynchronous; @property(nonatomic) NSTimeInterval timeout; @property(nonatomic, readonly) EXPExpect *to; @property(nonatomic, readonly) EXPExpect *toNot; @property(nonatomic, readonly) EXPExpect *notTo; @property(nonatomic, readonly) EXPExpect *will; @property(nonatomic, readonly) EXPExpect *willNot; @property(nonatomic, readonly) EXPExpect *(^after)(NSTimeInterval timeInterval); - (instancetype)initWithActualBlock:(id)actualBlock testCase:(id)testCase lineNumber:(int)lineNumber fileName:(const char *)fileName NS_DESIGNATED_INITIALIZER; + (EXPExpect *)expectWithActualBlock:(id)actualBlock testCase:(id)testCase lineNumber:(int)lineNumber fileName:(const char *)fileName; - (void)applyMatcher:(id)matcher; - (void)applyMatcher:(id)matcher to:(NSObject **)actual; @end @interface EXPDynamicPredicateMatcher : NSObject { EXPExpect *_expectation; SEL _selector; } - (instancetype)initWithExpectation:(EXPExpect *)expectation selector:(SEL)selector NS_DESIGNATED_INITIALIZER; @property (nonatomic, readonly, copy) void (^dispatch)(void); @end ================================================ FILE: Example/Pods/Expecta/Expecta/EXPExpect.m ================================================ #import "EXPExpect.h" #import "NSObject+Expecta.h" #import "Expecta.h" #import "EXPUnsupportedObject.h" #import "EXPMatcher.h" #import "EXPBlockDefinedMatcher.h" #import @implementation EXPExpect @dynamic actual, to, toNot, notTo, will, willNot, after; @synthesize actualBlock=_actualBlock, testCase=_testCase, negative=_negative, asynchronous=_asynchronous, timeout=_timeout, lineNumber=_lineNumber, fileName=_fileName; - (instancetype)initWithActualBlock:(id)actualBlock testCase:(id)testCase lineNumber:(int)lineNumber fileName:(const char *)fileName { self = [super init]; if(self) { self.actualBlock = actualBlock; self.testCase = testCase; self.negative = NO; self.asynchronous = NO; self.timeout = [Expecta asynchronousTestTimeout]; self.lineNumber = lineNumber; self.fileName = fileName; } return self; } - (void)dealloc { _actualBlock = nil; [super dealloc]; } + (EXPExpect *)expectWithActualBlock:(id)actualBlock testCase:(id)testCase lineNumber:(int)lineNumber fileName:(const char *)fileName { return [[[EXPExpect alloc] initWithActualBlock:actualBlock testCase:(id)testCase lineNumber:lineNumber fileName:fileName] autorelease]; } #pragma mark - - (EXPExpect *)to { return self; } - (EXPExpect *)toNot { self.negative = !self.negative; return self; } - (EXPExpect *)notTo { return [self toNot]; } - (EXPExpect *)will { self.asynchronous = YES; return self; } - (EXPExpect *)willNot { return self.will.toNot; } - (EXPExpect *(^)(NSTimeInterval))after { EXPExpect * (^block)(NSTimeInterval) = [^EXPExpect *(NSTimeInterval timeout) { self.asynchronous = YES; self.timeout = timeout; return self; } copy]; return [block autorelease]; } #pragma mark - - (id)actual { if(self.actualBlock) { return self.actualBlock(); } return nil; } - (void)applyMatcher:(id)matcher { id actual = [self actual]; [self applyMatcher:matcher to:&actual]; } - (void)applyMatcher:(id)matcher to:(NSObject **)actual { if([*actual isKindOfClass:[EXPUnsupportedObject class]]) { EXPFail(self.testCase, self.lineNumber, self.fileName, [NSString stringWithFormat:@"expecting a %@ is not supported", ((EXPUnsupportedObject *)*actual).type]); } else { BOOL failed = NO; if([matcher respondsToSelector:@selector(meetsPrerequesiteFor:)] && ![matcher meetsPrerequesiteFor:*actual]) { failed = YES; } else { BOOL matchResult = NO; if(self.asynchronous) { NSTimeInterval timeOut = self.timeout; NSDate *expiryDate = [NSDate dateWithTimeIntervalSinceNow:timeOut]; while(1) { matchResult = [matcher matches:*actual]; failed = self.negative ? matchResult : !matchResult; if(!failed || ([(NSDate *)[NSDate date] compare:expiryDate] == NSOrderedDescending)) { break; } [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]]; OSMemoryBarrier(); *actual = self.actual; } } else { matchResult = [matcher matches:*actual]; } failed = self.negative ? matchResult : !matchResult; } if(failed) { NSString *message = nil; if(self.negative) { if ([matcher respondsToSelector:@selector(failureMessageForNotTo:)]) { message = [matcher failureMessageForNotTo:*actual]; } } else { if ([matcher respondsToSelector:@selector(failureMessageForTo:)]) { message = [matcher failureMessageForTo:*actual]; } } if (message == nil) { message = @"Match Failed."; } EXPFail(self.testCase, self.lineNumber, self.fileName, message); } } self.negative = NO; } #pragma mark - Dynamic predicate dispatch - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { if ([self.actual respondsToSelector:aSelector]) { return [self.actual methodSignatureForSelector:aSelector]; } return [super methodSignatureForSelector:aSelector]; } - (void)forwardInvocation:(NSInvocation *)anInvocation { if ([self.actual respondsToSelector:anInvocation.selector]) { EXPDynamicPredicateMatcher *matcher = [[EXPDynamicPredicateMatcher alloc] initWithExpectation:self selector:anInvocation.selector]; [anInvocation setSelector:@selector(dispatch)]; [anInvocation invokeWithTarget:matcher]; [matcher release]; } else { [super forwardInvocation:anInvocation]; } } @end @implementation EXPDynamicPredicateMatcher - (instancetype)initWithExpectation:(EXPExpect *)expectation selector:(SEL)selector { if ((self = [super init])) { _expectation = expectation; _selector = selector; } return self; } - (BOOL)matches:(id)actual { return (BOOL)[actual performSelector:_selector]; } - (NSString *)failureMessageForTo:(id)actual { return [NSString stringWithFormat:@"expected %@ to be true", NSStringFromSelector(_selector)]; } - (NSString *)failureMessageForNotTo:(id)actual { return [NSString stringWithFormat:@"expected %@ to be false", NSStringFromSelector(_selector)]; } - (void (^)(void))dispatch { __block id blockExpectation = _expectation; return [[^{ [blockExpectation applyMatcher:self]; } copy] autorelease]; } @end ================================================ FILE: Example/Pods/Expecta/Expecta/EXPFloatTuple.h ================================================ #import @interface EXPFloatTuple : NSObject { float *_values; size_t _size; } @property (nonatomic, assign) float *values; @property (nonatomic, assign) size_t size; - (instancetype)initWithFloatValues:(float *)values size:(size_t)size NS_DESIGNATED_INITIALIZER; @end ================================================ FILE: Example/Pods/Expecta/Expecta/EXPFloatTuple.m ================================================ #import "EXPFloatTuple.h" @implementation EXPFloatTuple @synthesize values = _values, size = _size; - (instancetype)initWithFloatValues:(float *)values size:(size_t)size { if ((self = [super init])) { self.values = malloc(sizeof(float) * size); memcpy(self.values, values, sizeof(float) * size); self.size = size; } return self; } - (void)dealloc { free(self.values); [super dealloc]; } - (BOOL)isEqual:(id)object { if (![object isKindOfClass:[EXPFloatTuple class]]) return NO; EXPFloatTuple *other = (EXPFloatTuple *)object; if (self.size == other.size) { for (int i = 0; i < self.size; ++i) { if (self.values[i] != other.values[i]) return NO; } return YES; } return NO; } - (NSUInteger)hash { NSUInteger prime = 31; NSUInteger hash = 0; for (int i=0; i @protocol EXPMatcher - (BOOL)matches:(id)actual; @optional - (BOOL)meetsPrerequesiteFor:(id)actual; - (NSString *)failureMessageForTo:(id)actual; - (NSString *)failureMessageForNotTo:(id)actual; @end ================================================ FILE: Example/Pods/Expecta/Expecta/EXPUnsupportedObject.h ================================================ #import @interface EXPUnsupportedObject : NSObject { NSString *_type; } @property (nonatomic, retain) NSString *type; - (instancetype)initWithType:(NSString *)type NS_DESIGNATED_INITIALIZER; @end ================================================ FILE: Example/Pods/Expecta/Expecta/EXPUnsupportedObject.m ================================================ #import "EXPUnsupportedObject.h" @implementation EXPUnsupportedObject @synthesize type=_type; - (instancetype)initWithType:(NSString *)type { self = [super init]; if(self) { self.type = type; } return self; } - (void)dealloc { self.type = nil; [super dealloc]; } @end ================================================ FILE: Example/Pods/Expecta/Expecta/Expecta.h ================================================ #import //! Project version number for Expecta. FOUNDATION_EXPORT double ExpectaVersionNumber; //! Project version string for Expecta. FOUNDATION_EXPORT const unsigned char ExpectaVersionString[]; #import #import #import // Enable shorthand by default #define expect(...) EXP_expect((__VA_ARGS__)) #define failure(...) EXP_failure((__VA_ARGS__)) ================================================ FILE: Example/Pods/Expecta/Expecta/ExpectaObject.h ================================================ #import #define EXPObjectify(value) _EXPObjectify(@encode(__typeof__((value))), (value)) #define EXP_expect(actual) _EXP_expect(self, __LINE__, __FILE__, ^id{ return EXPObjectify((actual)); }) #define EXPMatcherInterface(matcherName, matcherArguments) _EXPMatcherInterface(matcherName, matcherArguments) #define EXPMatcherImplementationBegin(matcherName, matcherArguments) _EXPMatcherImplementationBegin(matcherName, matcherArguments) #define EXPMatcherImplementationEnd _EXPMatcherImplementationEnd #define EXPMatcherAliasImplementation(newMatcherName, oldMatcherName, matcherArguments) _EXPMatcherAliasImplementation(newMatcherName, oldMatcherName, matcherArguments) #define EXP_failure(message) EXPFail(self, __LINE__, __FILE__, message) @interface Expecta : NSObject + (NSTimeInterval)asynchronousTestTimeout; + (void)setAsynchronousTestTimeout:(NSTimeInterval)timeout; @end ================================================ FILE: Example/Pods/Expecta/Expecta/ExpectaObject.m ================================================ #import "ExpectaObject.h" @implementation Expecta static NSTimeInterval _asynchronousTestTimeout = 1.0; + (NSTimeInterval)asynchronousTestTimeout { return _asynchronousTestTimeout; } + (void)setAsynchronousTestTimeout:(NSTimeInterval)timeout { _asynchronousTestTimeout = timeout; } @end ================================================ FILE: Example/Pods/Expecta/Expecta/ExpectaSupport.h ================================================ #import "EXPExpect.h" #import "EXPBlockDefinedMatcher.h" #ifdef __cplusplus extern "C" { #endif id _EXPObjectify(const char *type, ...); EXPExpect *_EXP_expect(id testCase, int lineNumber, const char *fileName, EXPIdBlock actualBlock); void EXPFail(id testCase, int lineNumber, const char *fileName, NSString *message); NSString *EXPDescribeObject(id obj); void EXP_prerequisite(EXPBoolBlock block); void EXP_match(EXPBoolBlock block); void EXP_failureMessageForTo(EXPStringBlock block); void EXP_failureMessageForNotTo(EXPStringBlock block); #if __has_feature(objc_arc) #define _EXP_release(x) #define _EXP_autorelease(x) (x) #else #define _EXP_release(x) [x release] #define _EXP_autorelease(x) [x autorelease] #endif // workaround for the categories bug: http://developer.apple.com/library/mac/#qa/qa1490/_index.html #define EXPFixCategoriesBug(name) \ __attribute__((constructor)) static void EXPFixCategoriesBug##name() {} #define _EXPMatcherInterface(matcherName, matcherArguments) \ @interface EXPExpect (matcherName##Matcher) \ @property (nonatomic, readonly) void(^ matcherName) matcherArguments; \ @end #define _EXPMatcherImplementationBegin(matcherName, matcherArguments) \ EXPFixCategoriesBug(EXPMatcher##matcherName##Matcher); \ @implementation EXPExpect (matcherName##Matcher) \ @dynamic matcherName;\ - (void(^) matcherArguments) matcherName { \ EXPBlockDefinedMatcher *matcher = [[EXPBlockDefinedMatcher alloc] init]; \ [[[NSThread currentThread] threadDictionary] setObject:matcher forKey:@"EXP_currentMatcher"]; \ __block id actual = self.actual; \ __block void (^prerequisite)(EXPBoolBlock block) = ^(EXPBoolBlock block) { EXP_prerequisite(block); }; \ __block void (^match)(EXPBoolBlock block) = ^(EXPBoolBlock block) { EXP_match(block); }; \ __block void (^failureMessageForTo)(EXPStringBlock block) = ^(EXPStringBlock block) { EXP_failureMessageForTo(block); }; \ __block void (^failureMessageForNotTo)(EXPStringBlock block) = ^(EXPStringBlock block) { EXP_failureMessageForNotTo(block); }; \ prerequisite(nil); match(nil); failureMessageForTo(nil); failureMessageForNotTo(nil); \ void (^matcherBlock) matcherArguments = [^ matcherArguments { \ { #define _EXPMatcherImplementationEnd \ } \ [self applyMatcher:matcher to:&actual]; \ } copy]; \ _EXP_release(matcher); \ return _EXP_autorelease(matcherBlock); \ } \ @end #define _EXPMatcherAliasImplementation(newMatcherName, oldMatcherName, matcherArguments) \ EXPFixCategoriesBug(EXPMatcher##newMatcherName##Matcher); \ @implementation EXPExpect (newMatcherName##Matcher) \ @dynamic newMatcherName;\ - (void(^) matcherArguments) newMatcherName { \ return [self oldMatcherName]; \ }\ @end #ifdef __cplusplus } #endif ================================================ FILE: Example/Pods/Expecta/Expecta/ExpectaSupport.m ================================================ #import "ExpectaSupport.h" #import "NSValue+Expecta.h" #import "NSObject+Expecta.h" #import "EXPUnsupportedObject.h" #import "EXPFloatTuple.h" #import "EXPDoubleTuple.h" #import "EXPDefines.h" #import @interface NSObject (ExpectaXCTestRecordFailure) // suppress warning - (void)recordFailureWithDescription:(NSString *)description inFile:(NSString *)filename atLine:(NSUInteger)lineNumber expected:(BOOL)expected; @end id _EXPObjectify(const char *type, ...) { va_list v; va_start(v, type); id obj = nil; if(strcmp(type, @encode(char)) == 0) { char actual = (char)va_arg(v, int); obj = @(actual); } else if(strcmp(type, @encode(_Bool)) == 0) { _Static_assert(sizeof(_Bool) <= sizeof(int), "Expected _Bool to be subject to vararg type promotion"); _Bool actual = (_Bool)va_arg(v, int); obj = @(actual); } else if(strcmp(type, @encode(double)) == 0) { double actual = (double)va_arg(v, double); obj = @(actual); } else if(strcmp(type, @encode(float)) == 0) { float actual = (float)va_arg(v, double); obj = @(actual); } else if(strcmp(type, @encode(int)) == 0) { int actual = (int)va_arg(v, int); obj = @(actual); } else if(strcmp(type, @encode(long)) == 0) { long actual = (long)va_arg(v, long); obj = @(actual); } else if(strcmp(type, @encode(long long)) == 0) { long long actual = (long long)va_arg(v, long long); obj = @(actual); } else if(strcmp(type, @encode(short)) == 0) { short actual = (short)va_arg(v, int); obj = @(actual); } else if(strcmp(type, @encode(unsigned char)) == 0) { unsigned char actual = (unsigned char)va_arg(v, unsigned int); obj = @(actual); } else if(strcmp(type, @encode(unsigned int)) == 0) { unsigned int actual = (int)va_arg(v, unsigned int); obj = @(actual); } else if(strcmp(type, @encode(unsigned long)) == 0) { unsigned long actual = (unsigned long)va_arg(v, unsigned long); obj = @(actual); } else if(strcmp(type, @encode(unsigned long long)) == 0) { unsigned long long actual = (unsigned long long)va_arg(v, unsigned long long); obj = @(actual); } else if(strcmp(type, @encode(unsigned short)) == 0) { unsigned short actual = (unsigned short)va_arg(v, unsigned int); obj = @(actual); } else if(strstr(type, @encode(EXPBasicBlock)) != NULL) { // @encode(EXPBasicBlock) returns @? as of clang 4.1. // This condition must occur before the test for id/class type, // otherwise blocks will be treated as vanilla objects. id actual = va_arg(v, EXPBasicBlock); obj = [[actual copy] autorelease]; } else if((strstr(type, @encode(id)) != NULL) || (strstr(type, @encode(Class)) != 0)) { id actual = va_arg(v, id); obj = actual; } else if(strcmp(type, @encode(__typeof__(nil))) == 0) { obj = nil; } else if(strstr(type, "ff}{") != NULL) { //TODO: of course this only works for a 2x2 e.g. CGRect obj = [[[EXPFloatTuple alloc] initWithFloatValues:(float *)va_arg(v, float[4]) size:4] autorelease]; } else if(strstr(type, "=ff}") != NULL) { obj = [[[EXPFloatTuple alloc] initWithFloatValues:(float *)va_arg(v, float[2]) size:2] autorelease]; } else if(strstr(type, "=ffff}") != NULL) { obj = [[[EXPFloatTuple alloc] initWithFloatValues:(float *)va_arg(v, float[4]) size:4] autorelease]; } else if(strstr(type, "dd}{") != NULL) { //TODO: same here obj = [[[EXPDoubleTuple alloc] initWithDoubleValues:(double *)va_arg(v, double[4]) size:4] autorelease]; } else if(strstr(type, "=dd}") != NULL) { obj = [[[EXPDoubleTuple alloc] initWithDoubleValues:(double *)va_arg(v, double[2]) size:2] autorelease]; } else if(strstr(type, "=dddd}") != NULL) { obj = [[[EXPDoubleTuple alloc] initWithDoubleValues:(double *)va_arg(v, double[4]) size:4] autorelease]; } else if(type[0] == '{') { EXPUnsupportedObject *actual = [[[EXPUnsupportedObject alloc] initWithType:@"struct"] autorelease]; obj = actual; } else if(type[0] == '(') { EXPUnsupportedObject *actual = [[[EXPUnsupportedObject alloc] initWithType:@"union"] autorelease]; obj = actual; } else { void *actual = va_arg(v, void *); obj = (actual == NULL ? nil :[NSValue valueWithPointer:actual]); } if([obj isKindOfClass:[NSValue class]] && ![obj isKindOfClass:[NSNumber class]]) { [(NSValue *)obj set_EXP_objCType:type]; } va_end(v); return obj; } EXPExpect *_EXP_expect(id testCase, int lineNumber, const char *fileName, EXPIdBlock actualBlock) { return [EXPExpect expectWithActualBlock:actualBlock testCase:testCase lineNumber:lineNumber fileName:fileName]; } void EXPFail(id testCase, int lineNumber, const char *fileName, NSString *message) { NSLog(@"%s:%d %@", fileName, lineNumber, message); NSString *reason = [NSString stringWithFormat:@"%s:%d %@", fileName, lineNumber, message]; NSException *exception = [NSException exceptionWithName:@"Expecta Error" reason:reason userInfo:nil]; if(testCase && [testCase respondsToSelector:@selector(recordFailureWithDescription:inFile:atLine:expected:)]){ [testCase recordFailureWithDescription:message inFile:@(fileName) atLine:lineNumber expected:NO]; } else { [exception raise]; } } NSString *EXPDescribeObject(id obj) { if(obj == nil) { return @"nil/null"; } else if([obj isKindOfClass:[NSValue class]] && ![obj isKindOfClass:[NSNumber class]]) { const char *type = [(NSValue *)obj _EXP_objCType]; if(type) { if(strcmp(type, @encode(SEL)) == 0) { return [NSString stringWithFormat:@"@selector(%@)", NSStringFromSelector([obj pointerValue])]; } else if(strcmp(type, @encode(Class)) == 0) { return NSStringFromClass([obj pointerValue]); } } } NSString *description = [obj description]; if([obj isKindOfClass:[NSArray class]]) { NSMutableArray *arr = [NSMutableArray arrayWithCapacity:[obj count]]; for(id o in obj) { [arr addObject:EXPDescribeObject(o)]; } description = [NSString stringWithFormat:@"(%@)", [arr componentsJoinedByString:@", "]]; } else if([obj isKindOfClass:[NSSet class]] || [obj isKindOfClass:[NSOrderedSet class]]) { NSMutableArray *arr = [NSMutableArray arrayWithCapacity:[obj count]]; for(id o in obj) { [arr addObject:EXPDescribeObject(o)]; } description = [NSString stringWithFormat:@"{(%@)}", [arr componentsJoinedByString:@", "]]; } else if([obj isKindOfClass:[NSDictionary class]]) { NSMutableArray *arr = [NSMutableArray arrayWithCapacity:[obj count]]; for(id k in obj) { id v = obj[k]; [arr addObject:[NSString stringWithFormat:@"%@ = %@;",EXPDescribeObject(k), EXPDescribeObject(v)]]; } description = [NSString stringWithFormat:@"{%@}", [arr componentsJoinedByString:@" "]]; } else if([obj isKindOfClass:[NSAttributedString class]]) { description = [obj string]; } else { description = [description stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"]; } return description; } void EXP_prerequisite(EXPBoolBlock block) { [[[NSThread currentThread] threadDictionary][@"EXP_currentMatcher"] setPrerequisiteBlock:block]; } void EXP_match(EXPBoolBlock block) { [[[NSThread currentThread] threadDictionary][@"EXP_currentMatcher"] setMatchBlock:block]; } void EXP_failureMessageForTo(EXPStringBlock block) { [[[NSThread currentThread] threadDictionary][@"EXP_currentMatcher"] setFailureMessageForToBlock:block]; } void EXP_failureMessageForNotTo(EXPStringBlock block) { [[[NSThread currentThread] threadDictionary][@"EXP_currentMatcher"] setFailureMessageForNotToBlock:block]; } ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatcherHelpers.h ================================================ #import BOOL EXPIsValuePointer(NSValue *value); BOOL EXPIsNumberFloat(NSNumber *number); ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatcherHelpers.m ================================================ #import "EXPMatcherHelpers.h" BOOL EXPIsValuePointer(NSValue *value) { return [value objCType][0] == @encode(void *)[0]; } BOOL EXPIsNumberFloat(NSNumber *number) { return strcmp([number objCType], @encode(float)) == 0; } ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+beCloseTo.h ================================================ #import "Expecta.h" EXPMatcherInterface(_beCloseToWithin, (id expected, id within)); EXPMatcherInterface(beCloseToWithin, (id expected, id within)); #define beCloseTo(expected) _beCloseToWithin(EXPObjectify((expected)), nil) #define beCloseToWithin(expected, range) _beCloseToWithin(EXPObjectify((expected)), EXPObjectify((range))) ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+beCloseTo.m ================================================ #import "EXPMatchers+beCloseTo.h" #import "EXPMatcherHelpers.h" EXPMatcherImplementationBegin(_beCloseToWithin, (id expected, id within)) { prerequisite(^BOOL{ return [actual isKindOfClass:[NSNumber class]] && [expected isKindOfClass:[NSNumber class]] && ([within isKindOfClass:[NSNumber class]] || (within == nil)); }); match(^BOOL{ double actualValue = [actual doubleValue]; double expectedValue = [expected doubleValue]; if (within != nil) { double withinValue = [within doubleValue]; double lowerBound = expectedValue - withinValue; double upperBound = expectedValue + withinValue; return (actualValue >= lowerBound) && (actualValue <= upperBound); } else { double diff = fabs(actualValue - expectedValue); actualValue = fabs(actualValue); expectedValue = fabs(expectedValue); double largest = (expectedValue > actualValue) ? expectedValue : actualValue; return (diff <= largest * FLT_EPSILON); } }); failureMessageForTo(^NSString *{ if (within) { return [NSString stringWithFormat:@"expected %@ to be close to %@ within %@", EXPDescribeObject(actual), EXPDescribeObject(expected), EXPDescribeObject(within)]; } else { return [NSString stringWithFormat:@"expected %@ to be close to %@", EXPDescribeObject(actual), EXPDescribeObject(expected)]; } }); failureMessageForNotTo(^NSString *{ if (within) { return [NSString stringWithFormat:@"expected %@ not to be close to %@ within %@", EXPDescribeObject(actual), EXPDescribeObject(expected), EXPDescribeObject(within)]; } else { return [NSString stringWithFormat:@"expected %@ not to be close to %@", EXPDescribeObject(actual), EXPDescribeObject(expected)]; } }); } EXPMatcherImplementationEnd ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+beFalsy.h ================================================ #import "Expecta.h" EXPMatcherInterface(beFalsy, (void)); ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+beFalsy.m ================================================ #import "EXPMatchers+beFalsy.h" #import "EXPMatcherHelpers.h" EXPMatcherImplementationBegin(beFalsy, (void)) { match(^BOOL{ if([actual isKindOfClass:[NSNumber class]]) { return ![(NSNumber *)actual boolValue]; } else if([actual isKindOfClass:[NSValue class]]) { if(EXPIsValuePointer((NSValue *)actual)) { return ![(NSValue *)actual pointerValue]; } } return !actual; }); failureMessageForTo(^NSString *{ return [NSString stringWithFormat:@"expected: a falsy value, got: %@, which is truthy", EXPDescribeObject(actual)]; }); failureMessageForNotTo(^NSString *{ return [NSString stringWithFormat:@"expected: a non-falsy value, got: %@, which is falsy", EXPDescribeObject(actual)]; }); } EXPMatcherImplementationEnd ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+beGreaterThan.h ================================================ #import "Expecta.h" EXPMatcherInterface(_beGreaterThan, (id expected)); EXPMatcherInterface(beGreaterThan, (id expected)); #define beGreaterThan(expected) _beGreaterThan(EXPObjectify((expected))) ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+beGreaterThan.m ================================================ #import "EXPMatchers+beGreaterThan.h" #import "EXPMatcherHelpers.h" EXPMatcherImplementationBegin(_beGreaterThan, (id expected)) { match(^BOOL{ if ([actual respondsToSelector:@selector(compare:)]) { return [actual compare:expected] == NSOrderedDescending; } return NO; }); failureMessageForTo(^NSString *{ return [NSString stringWithFormat:@"expected: %@ to be greater than %@", EXPDescribeObject(actual), EXPDescribeObject(expected)]; }); failureMessageForNotTo(^NSString *{ return [NSString stringWithFormat:@"expected: %@ not to be greater than %@", EXPDescribeObject(actual), EXPDescribeObject(expected)]; }); } EXPMatcherImplementationEnd ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+beGreaterThanOrEqualTo.h ================================================ #import "Expecta.h" EXPMatcherInterface(_beGreaterThanOrEqualTo, (id expected)); EXPMatcherInterface(beGreaterThanOrEqualTo, (id expected)); #define beGreaterThanOrEqualTo(expected) _beGreaterThanOrEqualTo(EXPObjectify((expected))) ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+beGreaterThanOrEqualTo.m ================================================ #import "EXPMatchers+beGreaterThanOrEqualTo.h" #import "EXPMatcherHelpers.h" EXPMatcherImplementationBegin(_beGreaterThanOrEqualTo, (id expected)) { match(^BOOL{ if ([actual respondsToSelector:@selector(compare:)]) { return [actual compare:expected] != NSOrderedAscending; } return NO; }); failureMessageForTo(^NSString *{ return [NSString stringWithFormat:@"expected: %@ to be greater than or equal to %@", EXPDescribeObject(actual), EXPDescribeObject(expected)]; }); failureMessageForNotTo(^NSString *{ return [NSString stringWithFormat:@"expected: %@ not to be greater than or equal to %@", EXPDescribeObject(actual), EXPDescribeObject(expected)]; }); } EXPMatcherImplementationEnd ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+beIdenticalTo.h ================================================ #import "Expecta.h" EXPMatcherInterface(_beIdenticalTo, (void *expected)); EXPMatcherInterface(beIdenticalTo, (void *expected)); // to aid code completion #if __has_feature(objc_arc) #define beIdenticalTo(expected) _beIdenticalTo((__bridge void*)expected) #else #define beIdenticalTo(expected) _beIdenticalTo(expected) #endif ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+beIdenticalTo.m ================================================ #import "EXPMatchers+equal.h" #import "EXPMatcherHelpers.h" EXPMatcherImplementationBegin(_beIdenticalTo, (void *expected)) { match(^BOOL{ if(actual == expected) { return YES; } else if([actual isKindOfClass:[NSValue class]] && EXPIsValuePointer((NSValue *)actual)) { if([(NSValue *)actual pointerValue] == expected) { return YES; } } return NO; }); failureMessageForTo(^NSString *{ return [NSString stringWithFormat:@"expected: <%p>, got: <%p>", expected, actual]; }); failureMessageForNotTo(^NSString *{ return [NSString stringWithFormat:@"expected: not <%p>, got: <%p>", expected, actual]; }); } EXPMatcherImplementationEnd ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+beInTheRangeOf.h ================================================ #import "Expecta.h" EXPMatcherInterface(_beInTheRangeOf, (id expectedLowerBound, id expectedUpperBound)); EXPMatcherInterface(beInTheRangeOf, (id expectedLowerBound, id expectedUpperBound)); #define beInTheRangeOf(expectedLowerBound, expectedUpperBound) _beInTheRangeOf(EXPObjectify((expectedLowerBound)), EXPObjectify((expectedUpperBound))) ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+beInTheRangeOf.m ================================================ #import "EXPMatchers+beInTheRangeOf.h" #import "EXPMatcherHelpers.h" EXPMatcherImplementationBegin(_beInTheRangeOf, (id expectedLowerBound, id expectedUpperBound)) { match(^BOOL{ if ([actual respondsToSelector:@selector(compare:)]) { NSComparisonResult compareLowerBound = [expectedLowerBound compare: actual]; NSComparisonResult compareUpperBound = [expectedUpperBound compare: actual]; if (compareLowerBound == NSOrderedSame) { return YES; } if (compareUpperBound == NSOrderedSame) { return YES; } if ((compareLowerBound == NSOrderedAscending) && (compareUpperBound == NSOrderedDescending)) { return YES; } } return NO; }); failureMessageForTo(^NSString *{ return [NSString stringWithFormat:@"expected: %@ to be in the range [%@, %@] (inclusive)", EXPDescribeObject(actual), EXPDescribeObject(expectedLowerBound), EXPDescribeObject(expectedUpperBound)]; }); failureMessageForNotTo(^NSString *{ return [NSString stringWithFormat:@"expected: %@ not to be in the range [%@, %@] (inclusive)", EXPDescribeObject(actual), EXPDescribeObject(expectedLowerBound), EXPDescribeObject(expectedUpperBound)]; }); } EXPMatcherImplementationEnd ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+beInstanceOf.h ================================================ #import "Expecta.h" EXPMatcherInterface(beInstanceOf, (Class expected)); EXPMatcherInterface(beAnInstanceOf, (Class expected)); EXPMatcherInterface(beMemberOf, (Class expected)); EXPMatcherInterface(beAMemberOf, (Class expected)); ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+beInstanceOf.m ================================================ #import "EXPMatchers+beInstanceOf.h" EXPMatcherImplementationBegin(beInstanceOf, (Class expected)) { BOOL actualIsNil = (actual == nil); BOOL expectedIsNil = (expected == nil); prerequisite(^BOOL{ return !(actualIsNil || expectedIsNil); }); match(^BOOL{ return [actual isMemberOfClass:expected]; }); failureMessageForTo(^NSString *{ if(actualIsNil) return @"the actual value is nil/null"; if(expectedIsNil) return @"the expected value is nil/null"; return [NSString stringWithFormat:@"expected: an instance of %@, got: an instance of %@", [expected class], [actual class]]; }); failureMessageForNotTo(^NSString *{ if(actualIsNil) return @"the actual value is nil/null"; if(expectedIsNil) return @"the expected value is nil/null"; return [NSString stringWithFormat:@"expected: not an instance of %@, got: an instance of %@", [expected class], [actual class]]; }); } EXPMatcherImplementationEnd EXPMatcherAliasImplementation(beAnInstanceOf, beInstanceOf, (Class expected)); EXPMatcherAliasImplementation(beMemberOf, beInstanceOf, (Class expected)); EXPMatcherAliasImplementation(beAMemberOf, beInstanceOf, (Class expected)); ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+beKindOf.h ================================================ #import "Expecta.h" EXPMatcherInterface(beKindOf, (Class expected)); EXPMatcherInterface(beAKindOf, (Class expected)); ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+beKindOf.m ================================================ #import "EXPMatchers+beKindOf.h" EXPMatcherImplementationBegin(beKindOf, (Class expected)) { BOOL actualIsNil = (actual == nil); BOOL expectedIsNil = (expected == nil); prerequisite(^BOOL{ return !(actualIsNil || expectedIsNil); }); match(^BOOL{ return [actual isKindOfClass:expected]; }); failureMessageForTo(^NSString *{ if(actualIsNil) return @"the actual value is nil/null"; if(expectedIsNil) return @"the expected value is nil/null"; return [NSString stringWithFormat:@"expected: a kind of %@, got: an instance of %@, which is not a kind of %@", [expected class], [actual class], [expected class]]; }); failureMessageForNotTo(^NSString *{ if(actualIsNil) return @"the actual value is nil/null"; if(expectedIsNil) return @"the expected value is nil/null"; return [NSString stringWithFormat:@"expected: not a kind of %@, got: an instance of %@, which is a kind of %@", [expected class], [actual class], [expected class]]; }); } EXPMatcherImplementationEnd EXPMatcherAliasImplementation(beAKindOf, beKindOf, (Class expected)); ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+beLessThan.h ================================================ #import "Expecta.h" EXPMatcherInterface(_beLessThan, (id expected)); EXPMatcherInterface(beLessThan, (id expected)); #define beLessThan(expected) _beLessThan(EXPObjectify((expected))) ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+beLessThan.m ================================================ #import "EXPMatchers+beLessThan.h" #import "EXPMatcherHelpers.h" EXPMatcherImplementationBegin(_beLessThan, (id expected)) { match(^BOOL{ if ([actual respondsToSelector:@selector(compare:)]) { return [actual compare:expected] == NSOrderedAscending; } return NO; }); failureMessageForTo(^NSString *{ return [NSString stringWithFormat:@"expected: %@ to be less than %@", EXPDescribeObject(actual), EXPDescribeObject(expected)]; }); failureMessageForNotTo(^NSString *{ return [NSString stringWithFormat:@"expected: %@ not to be less than %@", EXPDescribeObject(actual), EXPDescribeObject(expected)]; }); } EXPMatcherImplementationEnd ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+beLessThanOrEqualTo.h ================================================ #import "Expecta.h" EXPMatcherInterface(_beLessThanOrEqualTo, (id expected)); EXPMatcherInterface(beLessThanOrEqualTo, (id expected)); #define beLessThanOrEqualTo(expected) _beLessThanOrEqualTo(EXPObjectify((expected))) ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+beLessThanOrEqualTo.m ================================================ #import "EXPMatchers+beLessThanOrEqualTo.h" #import "EXPMatcherHelpers.h" EXPMatcherImplementationBegin(_beLessThanOrEqualTo, (id expected)) { match(^BOOL{ if ([actual respondsToSelector:@selector(compare:)]) { return [actual compare:expected] != NSOrderedDescending; } return NO; }); failureMessageForTo(^NSString *{ return [NSString stringWithFormat:@"expected: %@ to be less than or equal to %@", EXPDescribeObject(actual), EXPDescribeObject(expected)]; }); failureMessageForNotTo(^NSString *{ return [NSString stringWithFormat:@"expected: %@ not to be less than or equal to %@", EXPDescribeObject(actual), EXPDescribeObject(expected)]; }); } EXPMatcherImplementationEnd ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+beNil.h ================================================ #import "Expecta.h" EXPMatcherInterface(beNil, (void)); EXPMatcherInterface(beNull, (void)); ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+beNil.m ================================================ #import "EXPMatchers+beNil.h" EXPMatcherImplementationBegin(beNil, (void)) { match(^BOOL{ return actual == nil; }); failureMessageForTo(^NSString *{ return [NSString stringWithFormat:@"expected: nil/null, got: %@", EXPDescribeObject(actual)]; }); failureMessageForNotTo(^NSString *{ return [NSString stringWithFormat:@"expected: not nil/null, got: %@", EXPDescribeObject(actual)]; }); } EXPMatcherImplementationEnd EXPMatcherAliasImplementation(beNull, beNil, (void)); ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+beSubclassOf.h ================================================ #import "Expecta.h" EXPMatcherInterface(beSubclassOf, (Class expected)); EXPMatcherInterface(beASubclassOf, (Class expected)); ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+beSubclassOf.m ================================================ #import "EXPMatchers+beSubclassOf.h" #import "NSValue+Expecta.h" #import EXPMatcherImplementationBegin(beSubclassOf, (Class expected)) { __block BOOL actualIsClass = YES; prerequisite(^BOOL { actualIsClass = class_isMetaClass(object_getClass(actual)); return actualIsClass; }); match(^BOOL{ return [actual isSubclassOfClass:expected]; }); failureMessageForTo(^NSString *{ if(!actualIsClass) return @"the actual value is not a Class"; return [NSString stringWithFormat:@"expected: a subclass of %@, got: a class %@, which is not a subclass of %@", [expected class], actual, [expected class]]; }); failureMessageForNotTo(^NSString *{ if(!actualIsClass) return @"the actual value is not a Class"; return [NSString stringWithFormat:@"expected: not a subclass of %@, got: a class %@, which is a subclass of %@", [expected class], actual, [expected class]]; }); } EXPMatcherImplementationEnd EXPMatcherAliasImplementation(beASubclassOf, beSubclassOf, (Class expected)); ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+beSupersetOf.h ================================================ #import "Expecta.h" EXPMatcherInterface(beSupersetOf, (id subset)); ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+beSupersetOf.m ================================================ #import "EXPMatchers+contain.h" EXPMatcherImplementationBegin(beSupersetOf, (id subset)) { BOOL actualIsCompatible = [actual isKindOfClass:[NSDictionary class]] || [actual respondsToSelector:@selector(containsObject:)]; BOOL subsetIsNil = (subset == nil); // For some instances the isKindOfClass: method returns false, even though // they are both actually dictionaries. e.g. Comparing a NSCFDictionary and a // NSDictionary. // Or in cases when you compare NSMutableArray (which implementation is __NSArrayM:NSMutableArray:NSArray) // and NSArray (which implementation is __NSArrayI:NSArray) BOOL bothAreIdenticalCollectionClasses = ([actual isKindOfClass:[NSDictionary class]] && [subset isKindOfClass:[NSDictionary class]]) || ([actual isKindOfClass:[NSArray class]] && [subset isKindOfClass:[NSArray class]]) || ([actual isKindOfClass:[NSSet class]] && [subset isKindOfClass:[NSSet class]]) || ([actual isKindOfClass:[NSOrderedSet class]] && [subset isKindOfClass:[NSOrderedSet class]]); BOOL classMatches = bothAreIdenticalCollectionClasses || [subset isKindOfClass:[actual class]]; prerequisite(^BOOL{ return actualIsCompatible && !subsetIsNil && classMatches; }); match(^BOOL{ if(!actualIsCompatible) return NO; if([actual isKindOfClass:[NSDictionary class]]) { for (id key in subset) { id actualValue = [actual valueForKey:key]; id subsetValue = [subset valueForKey:key]; if (![subsetValue isEqual:actualValue]) return NO; } } else { for (id object in subset) { if (![actual containsObject:object]) return NO; } } return YES; }); failureMessageForTo(^NSString *{ if(!actualIsCompatible) return [NSString stringWithFormat:@"%@ is not an instance of NSDictionary and does not implement -containsObject:", EXPDescribeObject(actual)]; if(subsetIsNil) return @"the expected value is nil/null"; if(!classMatches) return [NSString stringWithFormat:@"%@ does not match the class of %@", EXPDescribeObject(subset), EXPDescribeObject(actual)]; return [NSString stringWithFormat:@"expected %@ to be a superset of %@", EXPDescribeObject(actual), EXPDescribeObject(subset)]; }); failureMessageForNotTo(^NSString *{ if(!actualIsCompatible) return [NSString stringWithFormat:@"%@ is not an instance of NSDictionary and does not implement -containsObject:", EXPDescribeObject(actual)]; if(subsetIsNil) return @"the expected value is nil/null"; if(!classMatches) return [NSString stringWithFormat:@"%@ does not match the class of %@", EXPDescribeObject(subset), EXPDescribeObject(actual)]; return [NSString stringWithFormat:@"expected %@ not to be a superset of %@", EXPDescribeObject(actual), EXPDescribeObject(subset)]; }); } EXPMatcherImplementationEnd ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+beTruthy.h ================================================ #import "Expecta.h" EXPMatcherInterface(beTruthy, (void)); ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+beTruthy.m ================================================ #import "EXPMatchers+beTruthy.h" #import "EXPMatcherHelpers.h" EXPMatcherImplementationBegin(beTruthy, (void)) { match(^BOOL{ if([actual isKindOfClass:[NSNumber class]]) { return !![(NSNumber *)actual boolValue]; } else if([actual isKindOfClass:[NSValue class]]) { if(EXPIsValuePointer((NSValue *)actual)) { return !![(NSValue *)actual pointerValue]; } } return !!actual; }); failureMessageForTo(^NSString *{ return [NSString stringWithFormat:@"expected: a truthy value, got: %@, which is falsy", EXPDescribeObject(actual)]; }); failureMessageForNotTo(^NSString *{ return [NSString stringWithFormat:@"expected: a non-truthy value, got: %@, which is truthy", EXPDescribeObject(actual)]; }); } EXPMatcherImplementationEnd ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+beginWith.h ================================================ #import "Expecta.h" EXPMatcherInterface(beginWith, (id expected)); EXPMatcherInterface(startWith, (id expected)); ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+beginWith.m ================================================ #import "EXPMatchers+beginWith.h" EXPMatcherImplementationBegin(beginWith, (id expected)) { BOOL actualIsNil = (actual == nil); BOOL expectedIsNil = (expected == nil); //This condition allows the comparison of an immutable string or ordered collection to the mutable type of the same BOOL actualAndExpectedAreCompatible = (([actual isKindOfClass:[NSString class]] && [expected isKindOfClass:[NSString class]]) || ([actual isKindOfClass:[NSArray class]] && [expected isKindOfClass:[NSArray class]]) || ([actual isKindOfClass:[NSOrderedSet class]] && [expected isKindOfClass:[NSOrderedSet class]])); prerequisite(^BOOL { return actualAndExpectedAreCompatible; }); match(^BOOL { if ([actual isKindOfClass:[NSString class]]) { return [actual hasPrefix:expected]; } else if ([actual isKindOfClass:[NSArray class]]) { if ([expected count] > [actual count] || [expected count] == 0) { return NO; } NSArray *subArray = [actual subarrayWithRange:NSMakeRange(0, [expected count])]; return [subArray isEqualToArray:expected]; } else { if ([expected count] > [actual count] || [expected count] == 0) { return NO; } NSOrderedSet *subset = [NSOrderedSet orderedSetWithOrderedSet:actual range:NSMakeRange(0, [expected count]) copyItems:NO]; return [subset isEqualToOrderedSet:expected]; } }); failureMessageForTo(^NSString *{ if (actualIsNil) return @"the object is nil/null"; if (expectedIsNil) return @"the expected value is nil/null"; if (!actualAndExpectedAreCompatible) return [NSString stringWithFormat:@"%@ and %@ are not instances of one of %@, %@, or %@", EXPDescribeObject(actual), EXPDescribeObject(expected), [NSString class], [NSArray class], [NSOrderedSet class]]; return [NSString stringWithFormat:@"expected: %@ to begin with %@", EXPDescribeObject(actual), EXPDescribeObject(expected)]; }); failureMessageForNotTo(^NSString *{ if (actualIsNil) return @"the object is nil/null"; if (expectedIsNil) return @"the expected value is nil/null"; if (!actualAndExpectedAreCompatible) return [NSString stringWithFormat:@"%@ and %@ are not instances of one of %@, %@, or %@", EXPDescribeObject(actual), EXPDescribeObject(expected), [NSString class], [NSArray class], [NSOrderedSet class]]; return [NSString stringWithFormat:@"expected: %@ not to begin with %@", EXPDescribeObject(actual), EXPDescribeObject(expected)]; }); } EXPMatcherImplementationEnd EXPMatcherAliasImplementation(startWith, beginWith, (id expected)); ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+conformTo.h ================================================ #import "Expecta.h" EXPMatcherInterface(conformTo, (Protocol *expected)); ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+conformTo.m ================================================ #import "EXPMatchers+conformTo.h" #import "NSValue+Expecta.h" #import EXPMatcherImplementationBegin(conformTo, (Protocol *expected)) { BOOL actualIsNil = (actual == nil); BOOL expectedIsNil = (expected == nil); prerequisite(^BOOL{ return !(actualIsNil || expectedIsNil); }); match(^BOOL{ return [actual conformsToProtocol:expected]; }); failureMessageForTo(^NSString *{ if(actualIsNil) return @"the object is nil/null"; if(expectedIsNil) return @"the protocol is nil/null"; NSString *name = NSStringFromProtocol(expected); return [NSString stringWithFormat:@"expected: %@ to conform to %@", actual, name]; }); failureMessageForNotTo(^NSString *{ if(actualIsNil) return @"the object is nil/null"; if(expectedIsNil) return @"the protocol is nil/null"; NSString *name = NSStringFromProtocol(expected); return [NSString stringWithFormat:@"expected: %@ not to conform to %@", actual, name]; }); } EXPMatcherImplementationEnd ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+contain.h ================================================ #import "Expecta.h" EXPMatcherInterface(_contain, (id expected)); EXPMatcherInterface(contain, (id expected)); // to aid code completion #define contain(expected) _contain(EXPObjectify((expected))) ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+contain.m ================================================ #import "EXPMatchers+contain.h" EXPMatcherImplementationBegin(_contain, (id expected)) { BOOL actualIsCompatible = [actual isKindOfClass:[NSString class]] || [actual conformsToProtocol:@protocol(NSFastEnumeration)]; BOOL expectedIsNil = (expected == nil); prerequisite(^BOOL{ return actualIsCompatible && !expectedIsNil; }); match(^BOOL{ if(actualIsCompatible) { if([actual isKindOfClass:[NSString class]]) { return [(NSString *)actual rangeOfString:[expected description]].location != NSNotFound; } else { for (id object in actual) { if ([object isEqual:expected]) { return YES; } } } } return NO; }); failureMessageForTo(^NSString *{ if(!actualIsCompatible) return [NSString stringWithFormat:@"%@ is not an instance of NSString or NSFastEnumeration", EXPDescribeObject(actual)]; if(expectedIsNil) return @"the expected value is nil/null"; return [NSString stringWithFormat:@"expected %@ to contain %@", EXPDescribeObject(actual), EXPDescribeObject(expected)]; }); failureMessageForNotTo(^NSString *{ if(!actualIsCompatible) return [NSString stringWithFormat:@"%@ is not an instance of NSString or NSFastEnumeration", EXPDescribeObject(actual)]; if(expectedIsNil) return @"the expected value is nil/null"; return [NSString stringWithFormat:@"expected %@ not to contain %@", EXPDescribeObject(actual), EXPDescribeObject(expected)]; }); } EXPMatcherImplementationEnd ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+endWith.h ================================================ #import "Expecta.h" EXPMatcherInterface(endWith, (id expected)); ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+endWith.m ================================================ #import "EXPMatchers+endWith.h" EXPMatcherImplementationBegin(endWith, (id expected)) { BOOL actualIsNil = (actual == nil); BOOL expectedIsNil = (expected == nil); //This condition allows the comparison of an immutable string or ordered collection to the mutable type of the same BOOL actualAndExpectedAreCompatible = (([actual isKindOfClass:[NSString class]] && [expected isKindOfClass:[NSString class]]) || ([actual isKindOfClass:[NSArray class]] && [expected isKindOfClass:[NSArray class]]) || ([actual isKindOfClass:[NSOrderedSet class]] && [expected isKindOfClass:[NSOrderedSet class]])); prerequisite(^BOOL { return actualAndExpectedAreCompatible; }); match(^BOOL { if ([actual isKindOfClass:[NSString class]]) { return [actual hasSuffix:expected]; } else if ([actual isKindOfClass:[NSArray class]]) { if ([expected count] > [actual count] || [expected count] == 0) { return NO; } NSArray *subArray = [actual subarrayWithRange:NSMakeRange([actual count] - [expected count], [expected count])]; return [subArray isEqualToArray:expected]; } else { if ([expected count] > [actual count] || [expected count] == 0) { return NO; } NSOrderedSet *subset = [NSOrderedSet orderedSetWithOrderedSet:actual range:NSMakeRange([actual count] - [expected count], [expected count]) copyItems:NO]; return [subset isEqualToOrderedSet:expected]; } }); failureMessageForTo(^NSString *{ if (actualIsNil) return @"the object is nil/null"; if (expectedIsNil) return @"the expected value is nil/null"; if (!actualAndExpectedAreCompatible) return [NSString stringWithFormat:@"%@ and %@ are not instances of one of %@, %@, or %@", EXPDescribeObject(actual), EXPDescribeObject(expected), [NSString class], [NSArray class], [NSOrderedSet class]]; return [NSString stringWithFormat:@"expected: %@ to end with %@", EXPDescribeObject(actual), EXPDescribeObject(expected)]; }); failureMessageForNotTo(^NSString *{ if (actualIsNil) return @"the object is nil/null"; if (expectedIsNil) return @"the expected value is nil/null"; if (!actualAndExpectedAreCompatible) return [NSString stringWithFormat:@"%@ and %@ are not instances of one of %@, %@, or %@", EXPDescribeObject(actual), EXPDescribeObject(expected), [NSString class], [NSArray class], [NSOrderedSet class]]; return [NSString stringWithFormat:@"expected: %@ not to end with %@", EXPDescribeObject(actual), EXPDescribeObject(expected)]; }); } EXPMatcherImplementationEnd ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+equal.h ================================================ #import "Expecta.h" EXPMatcherInterface(_equal, (id expected)); EXPMatcherInterface(equal, (id expected)); // to aid code completion #define equal(...) _equal(EXPObjectify((__VA_ARGS__))) ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+equal.m ================================================ #import "EXPMatchers+equal.h" #import "EXPMatcherHelpers.h" EXPMatcherImplementationBegin(_equal, (id expected)) { match(^BOOL{ if((actual == expected) || [actual isEqual:expected]) { return YES; } else if([actual isKindOfClass:[NSNumber class]] && [expected isKindOfClass:[NSNumber class]]) { if([actual isKindOfClass:[NSDecimalNumber class]] || [expected isKindOfClass:[NSDecimalNumber class]]) { NSDecimalNumber *actualDecimalNumber = [NSDecimalNumber decimalNumberWithDecimal:[(NSNumber *) actual decimalValue]]; NSDecimalNumber *expectedDecimalNumber = [NSDecimalNumber decimalNumberWithDecimal:[(NSNumber *) expected decimalValue]]; return [actualDecimalNumber isEqualToNumber:expectedDecimalNumber]; } else { if(EXPIsNumberFloat((NSNumber *)actual) || EXPIsNumberFloat((NSNumber *)expected)) { return [(NSNumber *)actual floatValue] == [(NSNumber *)expected floatValue]; } } } return NO; }); failureMessageForTo(^NSString *{ return [NSString stringWithFormat:@"expected: %@, got: %@", EXPDescribeObject(expected), EXPDescribeObject(actual)]; }); failureMessageForNotTo(^NSString *{ return [NSString stringWithFormat:@"expected: not %@, got: %@", EXPDescribeObject(expected), EXPDescribeObject(actual)]; }); } EXPMatcherImplementationEnd ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+haveCountOf.h ================================================ #import "Expecta.h" EXPMatcherInterface(haveCountOf, (NSUInteger expected)); EXPMatcherInterface(haveCount, (NSUInteger expected)); EXPMatcherInterface(haveACountOf, (NSUInteger expected)); EXPMatcherInterface(haveLength, (NSUInteger expected)); EXPMatcherInterface(haveLengthOf, (NSUInteger expected)); EXPMatcherInterface(haveALengthOf, (NSUInteger expected)); #define beEmpty() haveCountOf(0) ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+haveCountOf.m ================================================ #import "EXPMatchers+haveCountOf.h" EXPMatcherImplementationBegin(haveCountOf, (NSUInteger expected)) { BOOL actualIsStringy = [actual isKindOfClass:[NSString class]] || [actual isKindOfClass:[NSAttributedString class]]; BOOL actualIsCompatible = actualIsStringy || [actual respondsToSelector:@selector(count)]; prerequisite(^BOOL{ return actualIsCompatible; }); NSUInteger (^count)(id) = ^(id actual) { if(actualIsStringy) { return [actual length]; } else { return [actual count]; } }; match(^BOOL{ if(actualIsCompatible) { return count(actual) == expected; } return NO; }); failureMessageForTo(^NSString *{ if(!actualIsCompatible) return [NSString stringWithFormat:@"%@ is not an instance of NSString, NSAttributedString, NSArray, NSSet, NSOrderedSet, or NSDictionary", EXPDescribeObject(actual)]; return [NSString stringWithFormat:@"expected %@ to have a count of %zi but got %zi", EXPDescribeObject(actual), expected, count(actual)]; }); failureMessageForNotTo(^NSString *{ if(!actualIsCompatible) return [NSString stringWithFormat:@"%@ is not an instance of NSString, NSAttributedString, NSArray, NSSet, NSOrderedSet, or NSDictionary", EXPDescribeObject(actual)]; return [NSString stringWithFormat:@"expected %@ not to have a count of %zi", EXPDescribeObject(actual), expected]; }); } EXPMatcherImplementationEnd EXPMatcherAliasImplementation(haveCount, haveCountOf, (NSUInteger expected)); EXPMatcherAliasImplementation(haveACountOf, haveCountOf, (NSUInteger expected)); EXPMatcherAliasImplementation(haveLength, haveCountOf, (NSUInteger expected)); EXPMatcherAliasImplementation(haveLengthOf, haveCountOf, (NSUInteger expected)); EXPMatcherAliasImplementation(haveALengthOf, haveCountOf, (NSUInteger expected)); ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+match.h ================================================ #import "Expecta.h" EXPMatcherInterface(match, (NSString *expected)); ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+match.m ================================================ #import "EXPMatchers+match.h" #import "EXPMatcherHelpers.h" EXPMatcherImplementationBegin(match, (NSString *expected)) { BOOL actualIsNil = (actual == nil); BOOL expectedIsNil = (expected == nil); __block NSRegularExpression *regex = nil; __block NSError *regexError = nil; prerequisite (^BOOL { BOOL nilInput = (actualIsNil || expectedIsNil); if (!nilInput) { regex = [NSRegularExpression regularExpressionWithPattern:expected options:0 error:®exError]; } return !nilInput && regex; }); match(^BOOL { NSRange range = [regex rangeOfFirstMatchInString:actual options:0 range:NSMakeRange(0, [actual length])]; return !NSEqualRanges(range, NSMakeRange(NSNotFound, 0)); }); failureMessageForTo(^NSString *{ if (actualIsNil) return @"the object is nil/null"; if (expectedIsNil) return @"the expression is nil/null"; if (regexError) return [NSString stringWithFormat:@"unable to create regular expression from given parameter: %@", [regexError localizedDescription]]; return [NSString stringWithFormat:@"expected: %@ to match to %@", EXPDescribeObject(actual), expected]; }); failureMessageForNotTo(^NSString *{ if (actualIsNil) return @"the object is nil/null"; if (expectedIsNil) return @"the expression is nil/null"; if (regexError) return [NSString stringWithFormat:@"unable to create regular expression from given parameter: %@", [regexError localizedDescription]]; return [NSString stringWithFormat:@"expected: %@ not to match to %@", EXPDescribeObject(actual), expected]; }); } EXPMatcherImplementationEnd ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+postNotification.h ================================================ #import "Expecta.h" EXPMatcherInterface(postNotification, (id expectedNotification)); EXPMatcherInterface(notify, (id expectedNotification)); ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+postNotification.m ================================================ #import "EXPMatchers+postNotification.h" @implementation NSNotification (EXPEquality) - (BOOL)exp_isFunctionallyEqualTo:(NSNotification *)otherNotification { if (![otherNotification isKindOfClass:[NSNotification class]]) return NO; BOOL namesMatch = [otherNotification.name isEqualToString:self.name]; BOOL objectsMatch = YES; if (otherNotification.object || self.object) { objectsMatch = [otherNotification.object isEqual:self.object]; } BOOL userInfoMatches = YES; if (otherNotification.userInfo || self.userInfo) { userInfoMatches = [otherNotification.userInfo isEqual:self.userInfo]; } return (namesMatch && objectsMatch && userInfoMatches); } @end EXPMatcherImplementationBegin(postNotification, (id expected)){ BOOL actualIsNil = (actual == nil); BOOL expectedIsNil = (expected == nil); BOOL isNotification = [expected isKindOfClass:[NSNotification class]]; BOOL isName = [expected isKindOfClass:[NSString class]]; __block NSString *expectedName; __block BOOL expectedNotificationOccurred = NO; __block id observer; prerequisite(^BOOL{ expectedNotificationOccurred = NO; if (actualIsNil || expectedIsNil) return NO; if (isNotification) { expectedName = [expected name]; }else if(isName) { expectedName = expected; }else{ return NO; } observer = [[NSNotificationCenter defaultCenter] addObserverForName:expectedName object:nil queue:nil usingBlock:^(NSNotification *note){ if (isNotification) { expectedNotificationOccurred |= [expected exp_isFunctionallyEqualTo:note]; }else{ expectedNotificationOccurred = YES; } }]; ((EXPBasicBlock)actual)(); return YES; }); match(^BOOL{ if(expectedNotificationOccurred) { [[NSNotificationCenter defaultCenter] removeObserver:observer]; } return expectedNotificationOccurred; }); failureMessageForTo(^NSString *{ if (observer) { [[NSNotificationCenter defaultCenter] removeObserver:observer]; } if(actualIsNil) return @"the actual value is nil/null"; if(expectedIsNil) return @"the expected value is nil/null"; if(!(isNotification || isName)) return @"the actual value is not a notification or string"; return [NSString stringWithFormat:@"expected: %@, got: none",expectedName]; }); failureMessageForNotTo(^NSString *{ if (observer) { [[NSNotificationCenter defaultCenter] removeObserver:observer]; } if(actualIsNil) return @"the actual value is nil/null"; if(expectedIsNil) return @"the expected value is nil/null"; if(!(isNotification || isName)) return @"the actual value is not a notification or string"; return [NSString stringWithFormat:@"expected: none, got: %@", expectedName]; }); } EXPMatcherImplementationEnd EXPMatcherAliasImplementation(notify, postNotification, (id expectedNotification)) ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+raise.h ================================================ #import "Expecta.h" EXPMatcherInterface(raise, (NSString *expectedExceptionName)); #define raiseAny() raise(nil) ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+raise.m ================================================ #import "EXPMatchers+raise.h" #import "EXPDefines.h" EXPMatcherImplementationBegin(raise, (NSString *expectedExceptionName)) { __block NSException *exceptionCaught = nil; match(^BOOL{ BOOL expectedExceptionCaught = NO; @try { ((EXPBasicBlock)actual)(); } @catch(NSException *e) { exceptionCaught = e; expectedExceptionCaught = (expectedExceptionName == nil) || [[exceptionCaught name] isEqualToString:expectedExceptionName]; } return expectedExceptionCaught; }); failureMessageForTo(^NSString *{ return [NSString stringWithFormat:@"expected: %@, got: %@", expectedExceptionName ? expectedExceptionName : @"any exception", exceptionCaught ? [exceptionCaught name] : @"no exception"]; }); failureMessageForNotTo(^NSString *{ return [NSString stringWithFormat:@"expected: %@, got: %@", expectedExceptionName ? [NSString stringWithFormat:@"not %@", expectedExceptionName] : @"no exception", exceptionCaught ? [exceptionCaught name] : @"no exception"]; }); } EXPMatcherImplementationEnd ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+raiseWithReason.h ================================================ #import "Expecta.h" EXPMatcherInterface(raiseWithReason, (NSString *expectedExceptionName, NSString *expectedReason)); ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+raiseWithReason.m ================================================ #import "EXPMatchers+raiseWithReason.h" #import "EXPDefines.h" EXPMatcherImplementationBegin(raiseWithReason, (NSString *expectedExceptionName, NSString *expectedReason)) { __block NSException *exceptionCaught = nil; match(^BOOL{ BOOL expectedExceptionCaught = NO; @try { ((EXPBasicBlock)actual)(); } @catch(NSException *e) { exceptionCaught = e; expectedExceptionCaught = (((expectedExceptionName == nil) || [[exceptionCaught name] isEqualToString:expectedExceptionName]) && ((expectedReason == nil) || ([[exceptionCaught reason] isEqualToString:expectedReason]))); } return expectedExceptionCaught; }); failureMessageForTo(^NSString *{ return [NSString stringWithFormat:@"expected: %@ (%@), got: %@ (%@)", expectedExceptionName ?: @"any exception", expectedReason ?: @"any reason", exceptionCaught ? [exceptionCaught name] : @"no exception", exceptionCaught ? [exceptionCaught reason] : @""]; }); failureMessageForNotTo(^NSString *{ return [NSString stringWithFormat:@"expected: %@ (%@), got: %@ (%@)", expectedExceptionName ? [NSString stringWithFormat:@"not %@", expectedExceptionName] : @"no exception", expectedReason ? [NSString stringWithFormat:@"not '%@'", expectedReason] : @"no reason", exceptionCaught ? [exceptionCaught name] : @"no exception", exceptionCaught ? [exceptionCaught reason] : @"no reason"]; }); } EXPMatcherImplementationEnd ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+respondTo.h ================================================ #import "Expecta.h" EXPMatcherInterface(respondTo, (SEL expected)); ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers+respondTo.m ================================================ #import "EXPMatchers+respondTo.h" #import "EXPMatcherHelpers.h" EXPMatcherImplementationBegin(respondTo, (SEL expected)) { BOOL actualIsNil = (actual == nil); BOOL expectedIsNull = (expected == NULL); prerequisite (^BOOL { return !(actualIsNil || expectedIsNull); }); match(^BOOL { return [actual respondsToSelector:expected]; }); failureMessageForTo(^NSString *{ if (actualIsNil) return @"the object is nil/null"; if (expectedIsNull) return @"the selector is null"; return [NSString stringWithFormat:@"expected: %@ to respond to %@", EXPDescribeObject(actual), NSStringFromSelector(expected)]; }); failureMessageForNotTo(^NSString *{ if (actualIsNil) return @"the object is nil/null"; if (expectedIsNull) return @"the selector is null"; return [NSString stringWithFormat:@"expected: %@ not to respond to %@", EXPDescribeObject(actual), NSStringFromSelector(expected)]; }); } EXPMatcherImplementationEnd ================================================ FILE: Example/Pods/Expecta/Expecta/Matchers/EXPMatchers.h ================================================ #import "EXPMatchers+beNil.h" #import "EXPMatchers+equal.h" #import "EXPMatchers+beInstanceOf.h" #import "EXPMatchers+beKindOf.h" #import "EXPMatchers+beSubclassOf.h" #import "EXPMatchers+conformTo.h" #import "EXPMatchers+beTruthy.h" #import "EXPMatchers+beFalsy.h" #import "EXPMatchers+contain.h" #import "EXPMatchers+beSupersetOf.h" #import "EXPMatchers+haveCountOf.h" #import "EXPMatchers+beIdenticalTo.h" #import "EXPMatchers+beGreaterThan.h" #import "EXPMatchers+beGreaterThanOrEqualTo.h" #import "EXPMatchers+beLessThan.h" #import "EXPMatchers+beLessThanOrEqualTo.h" #import "EXPMatchers+beInTheRangeOf.h" #import "EXPMatchers+beCloseTo.h" #import "EXPMatchers+raise.h" #import "EXPMatchers+raiseWithReason.h" #import "EXPMatchers+respondTo.h" #import "EXPMatchers+postNotification.h" #import "EXPMatchers+beginWith.h" #import "EXPMatchers+endWith.h" #import "EXPMatchers+match.h" ================================================ FILE: Example/Pods/Expecta/Expecta/NSObject+Expecta.h ================================================ #import @interface NSObject (Expecta) - (void)recordFailureWithDescription:(NSString *)description inFile:(NSString *)filename atLine:(NSUInteger)lineNumber expected:(BOOL)expected; @end ================================================ FILE: Example/Pods/Expecta/Expecta/NSValue+Expecta.h ================================================ #import @interface NSValue (Expecta) @property (nonatomic) const char *_EXP_objCType; @end ================================================ FILE: Example/Pods/Expecta/Expecta/NSValue+Expecta.m ================================================ #import "NSValue+Expecta.h" #import #import "Expecta.h" EXPFixCategoriesBug(NSValue_Expecta); @implementation NSValue (Expecta) static char _EXP_typeKey; - (const char *)_EXP_objCType { return [(NSString *)objc_getAssociatedObject(self, &_EXP_typeKey) cStringUsingEncoding:NSASCIIStringEncoding]; } - (void)set_EXP_objCType:(const char *)_EXP_objCType { objc_setAssociatedObject(self, &_EXP_typeKey, @(_EXP_objCType), OBJC_ASSOCIATION_COPY_NONATOMIC); } @end ================================================ FILE: Example/Pods/Expecta/LICENSE ================================================ Copyright (c) 2011-2015 Specta Team - https://github.com/specta Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Example/Pods/Expecta/README.md ================================================ #Expecta [![Build Status](http://img.shields.io/travis/specta/expecta/master.svg?style=flat)](https://travis-ci.org/specta/expecta) [![Pod Version](http://img.shields.io/cocoapods/v/Expecta.svg?style=flat)](http://cocoadocs.org/docsets/Expecta/) [![Pod Platform](http://img.shields.io/cocoapods/p/Expecta.svg?style=flat)](http://cocoadocs.org/docsets/Expecta/) [![Pod License](http://img.shields.io/cocoapods/l/Expecta.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0.html) A matcher framework for Objective-C and Cocoa. ## Introduction The main advantage of using Expecta over other matcher frameworks is that you do not have to specify the data types. Also, the syntax of Expecta matchers is much more readable and does not suffer from parenthesitis. ```objective-c expect(@"foo").to.equal(@"foo"); // `to` is a syntactic sugar and can be safely omitted. expect(foo).notTo.equal(1); expect([bar isBar]).to.equal(YES); expect(baz).to.equal(3.14159); ``` Expecta is framework-agnostic: it works well with XCTest and XCTest-compatible test frameworks such as [Specta](http://github.com/petejkim/specta/). ## Setup You can setup Expecta using [Carthage](https://github.com/Carthage/Carthage), [CocoaPods](http://github.com/CocoaPods/CocoaPods) or [completely manually](#setting-up-manually). ### Carthage 1. Add Expecta to your project's `Cartfile.private`: ```ruby github "specta/expecta" "master" ``` 2. Run `carthage update` in your project directory. 3. Drag the appropriate **Expecta.framework** for your platform (located in `Carthage/Build/`) into your application’s Xcode project, and add it to your test target(s). ### CocoaPods 1. Add Expecta to your project's `Podfile`: ```ruby target :MyApp do # Your app's dependencies end target :MyAppTests do pod 'Expecta', '~> 0.2.4' end ``` 2. Run `pod update` or `pod install` in your project directory. ### Setting Up Manually 1. Clone Expecta from Github. 2. Run `rake` in your project directory to build the frameworks and libraries. 3. Add a Cocoa or Cocoa Touch Unit Testing Bundle target to your Xcode project if you don't already have one. 4. For **OS X projects**, copy and add `Expecta.framework` in the `Products/osx` folder to your project's test target. For **iOS projects**, copy and add `Expecta.framework` in the `Products/ios` folder to your project's test target. You can also use `libExpecta.a` if you prefer to link Expecta as a static library — iOS 7.x and below require this. 6. Add `-ObjC` and `-all_load` to the **Other Linker Flags** build setting for the test target in your Xcode project. 7. You can now use Expecta in your test classes by adding the following import: ```objective-c @import Expecta; // If you're using Expecta.framework // OR #import // If you're using the static library, or the framework ``` ## Built-in Matchers > `expect(x).to.equal(y);` compares objects or primitives x and y and passes if they are identical (==) or equivalent isEqual:). > `expect(x).to.beIdenticalTo(y);` compares objects x and y and passes if they are identical and have the same memory address. > `expect(x).to.beNil();` passes if x is nil. > `expect(x).to.beTruthy();` passes if x evaluates to true (non-zero). > `expect(x).to.beFalsy();` passes if x evaluates to false (zero). > `expect(x).to.contain(y);` passes if an instance of NSArray or NSString x contains y. > `expect(x).to.beSupersetOf(y);` passes if an instance of NSArray, NSSet, NSDictionary or NSOrderedSet x contains all elements of y. > `expect(x).to.haveCountOf(y);` passes if an instance of NSArray, NSSet, NSDictionary or NSString x has a count or length of y. > `expect(x).to.beEmpty();` passes if an instance of NSArray, NSSet, NSDictionary or NSString x has a count or length of . > `expect(x).to.beInstanceOf([Foo class]);` passes if x is an instance of a class Foo. > `expect(x).to.beKindOf([Foo class]);` passes if x is an instance of a class Foo or if x is an instance of any class that inherits from the class Foo. > `expect([Foo class]).to.beSubclassOf([Bar class]);` passes if the class Foo is a subclass of the class Bar or if it is identical to the class Bar. Use beKindOf() for class clusters. > `expect(x).to.beLessThan(y);` passes if `x` is less than `y`. > `expect(x).to.beLessThanOrEqualTo(y);` passes if `x` is less than or equal to `y`. > `expect(x).to.beGreaterThan(y);` passes if `x` is greater than `y`. > `expect(x).to.beGreaterThanOrEqualTo(y);` passes if `x` is greater than or equal to `y`. > `expect(x).to.beInTheRangeOf(y,z);` passes if `x` is in the range of `y` and `z`. > `expect(x).to.beCloseTo(y);` passes if `x` is close to `y`. > `expect(x).to.beCloseToWithin(y, z);` passes if `x` is close to `y` within `z`. > `expect(^{ /* code */ }).to.raise(@"ExceptionName");` passes if a given block of code raises an exception named `ExceptionName`. > `expect(^{ /* code */ }).to.raiseAny();` passes if a given block of code raises any exception. > `expect(x).to.conformTo(y);` passes if `x` conforms to the protocol `y`. > `expect(x).to.respondTo(y);` passes if `x` responds to the selector `y`. > `expect(^{ /* code */ }).to.notify(@"NotificationName");` passes if a given block of code generates an NSNotification amed `NotificationName`. > `expect(^{ /* code */ }).to.notify(notification);` passes if a given block of code generates an NSNotification equal to the passed `notification`. > `expect(x).to.beginWith(y);` passes if an instance of NSString, NSArray, or NSOrderedSet `x` begins with `y`. Also liased by `startWith` > `expect(x).to.endWith(y);` passes if an instance of NSString, NSArray, or NSOrderedSet `x` ends with `y`. > `expect(x).to.match(y);` passes if an instance of NSString `x` matches regular expression (given as NSString) `y` one or more times. ## Inverting Matchers Every matcher's criteria can be inverted by prepending `.notTo` or `.toNot`: >`expect(x).notTo.equal(y);` compares objects or primitives x and y and passes if they are *not* equivalent. ## Asynchronous Testing Every matcher can be made to perform asynchronous testing by prepending `.will`, `.willNot` or `after(...)`: >`expect(x).will.beNil();` passes if x becomes nil before the default timeout. > >`expect(x).willNot.beNil();` passes if x becomes non-nil before the default timeout. > >`expect(x).after(3).to.beNil();` passes if x becoms nil after 3.0 seconds. > >`expect(x).after(2.5).notTo.equal(42);` passes if x doesn't equal 42 after 2.5 seconds. The default timeout is 1.0 second and is used for all matchers if not otherwise specified. This setting can be changed by calling `[Expecta setAsynchronousTestTimeout:x]`, where `x` is the desired timeout in seconds. ```objective-c describe(@"Foo", ^{ beforeAll(^{ // All asynchronous matching using `will` and `willNot` // will have a timeout of 2.0 seconds [Expecta setAsynchronousTestTimeout:2]; }); it(@"will not be nil", ^{ // Test case where default timeout is used expect(foo).willNot.beNil(); }); it(@"should equal 42 after 3 seconds", ^{ // Signle case where timeout differs from the default expect(foo).after(3).to.equal(42); }); }); ``` ## Forced Failing You can fail a test by using the `failure` attribute. This can be used to test branching. > `failure(@"This should not happen");` outright fails a test. ## Writing New Matchers Writing a new matcher is easy with special macros provided by Expecta. Take a look at how `.beKindOf()` matcher is defined: `EXPMatchers+beKindOf.h` ```objective-c #import "Expecta.h" EXPMatcherInterface(beKindOf, (Class expected)); // 1st argument is the name of the matcher function // 2nd argument is the list of arguments that may be passed in the function // call. // Multiple arguments are fine. (e.g. (int foo, float bar)) #define beAKindOf beKindOf ``` `EXPMatchers+beKindOf.m` ```objective-c #import "EXPMatchers+beKindOf.h" EXPMatcherImplementationBegin(beKindOf, (Class expected)) { BOOL actualIsNil = (actual == nil); BOOL expectedIsNil = (expected == nil); prerequisite(^BOOL { return !(actualIsNil || expectedIsNil); // Return `NO` if matcher should fail whether or not the result is inverted // using `.Not`. }); match(^BOOL { return [actual isKindOfClass:expected]; // Return `YES` if the matcher should pass, `NO` if it should not. // The actual value/object is passed as `actual`. // Please note that primitive values will be wrapped in NSNumber/NSValue. }); failureMessageForTo(^NSString * { if (actualIsNil) return @"the actual value is nil/null"; if (expectedIsNil) return @"the expected value is nil/null"; return [NSString stringWithFormat:@"expected: a kind of %@, " "got: an instance of %@, which is not a kind of %@", [expected class], [actual class], [expected class]]; // Return the message to be displayed when the match function returns `YES`. }); failureMessageForNotTo(^NSString * { if (actualIsNil) return @"the actual value is nil/null"; if (expectedIsNil) return @"the expected value is nil/null"; return [NSString stringWithFormat:@"expected: not a kind of %@, " "got: an instance of %@, which is a kind of %@", [expected class], [actual class], [expected class]]; // Return the message to be displayed when the match function returns `NO`. }); } EXPMatcherImplementationEnd ``` ## Dynamic Predicate Matchers It is possible to add predicate matchers by simply defining the matcher interface, with the matcher implementation being handled at runtime by delegating to the predicate method on your object. For instance, if you have the following class: ```objc @interface LightSwitch : NSObject @property (nonatomic, assign, getter=isTurnedOn) BOOL turnedOn; @end @implementation LightSwitch @synthesize turnedOn; @end ``` The normal way to write an assertion that the switch is turned on would be: ```objc expect([lightSwitch isTurnedOn]).to.beTruthy(); ``` However, if we define a custom predicate matcher: ```objc EXPMatcherInterface(isTurnedOn, (void)); ``` (Note: we haven't defined the matcher implementation, just it's interface) You can now write your assertion as follows: ```objc expect(lightSwitch).isTurnedOn(); ``` ## Contribution Guidelines * Please use only spaces and indent 2 spaces at a time. * Please prefix instance variable names with a single underscore (`_`). * Please prefix custom classes and functions defined in the global scope with `EXP`. ## License Copyright (c) 2012-2015 [Specta Team](https://github.com/specta?tab=members). This software is licensed under the [MIT License](http://github.com/specta/specta/raw/master/LICENSE). ================================================ FILE: Example/Pods/Expecta+Snapshots/EXPMatchers+FBSnapshotTest.h ================================================ // // EXPMatchers+FBSnapshotTest.h // Artsy // // Created by Daniel Doubrovkine on 1/14/14. // Copyright (c) 2014 Artsy Inc. All rights reserved. // #import @interface EXPExpectFBSnapshotTest : NSObject @end /// Set the default folder for image tests to run in extern void setGlobalReferenceImageDir(char *reference); EXPMatcherInterface(haveValidSnapshot, (void)); EXPMatcherInterface(recordSnapshot, (void)); EXPMatcherInterface(haveValidSnapshotNamed, (NSString *snapshot)); EXPMatcherInterface(recordSnapshotNamed, (NSString *snapshot)); ================================================ FILE: Example/Pods/Expecta+Snapshots/EXPMatchers+FBSnapshotTest.m ================================================ // // EXPMatchers+FBSnapshotTest.h // Artsy // // Created by Daniel Doubrovkine on 1/14/14. // Copyright (c) 2014 Artsy Inc. All rights reserved. // #import "EXPMatchers+FBSnapshotTest.h" #import #import @interface EXPExpectFBSnapshotTest() @property (nonatomic, strong) NSString *referenceImagesDirectory; @end @implementation EXPExpectFBSnapshotTest + (id)instance { static EXPExpectFBSnapshotTest *instance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[self alloc] init]; }); return instance; } + (BOOL)compareSnapshotOfViewOrLayer:(id)viewOrLayer snapshot:(NSString *)snapshot testCase:(id)testCase record:(BOOL)record referenceDirectory:(NSString *)referenceDirectory error:(NSError **)error { FBSnapshotTestController *snapshotController = [[FBSnapshotTestController alloc] initWithTestClass:[testCase class]]; snapshotController.recordMode = record; snapshotController.referenceImagesDirectory = referenceDirectory; if (! snapshotController.referenceImagesDirectory) { [NSException raise:@"Missing value for referenceImagesDirectory" format:@"Call [[EXPExpectFBSnapshotTest instance] setReferenceImagesDirectory"]; } return [snapshotController compareSnapshotOfViewOrLayer:viewOrLayer selector:NSSelectorFromString(snapshot) identifier:nil error:error]; } + (NSString *)combinedError:(NSString *)message test:(NSString *)test error:(NSError *)error { NSAssert(message, @"missing message"); NSAssert(test, @"missing test name"); NSMutableArray *ary = [NSMutableArray array]; [ary addObject:[NSString stringWithFormat:@"%@ %@", message, test]]; for(NSString *key in error.userInfo.keyEnumerator) { [ary addObject:[NSString stringWithFormat:@" %@: %@", key, [error.userInfo valueForKey:key]]]; } return [ary componentsJoinedByString:@"\n"]; } @end void setGlobalReferenceImageDir(char *reference) { NSString *referenceImagesDirectory = [NSString stringWithFormat:@"%s", reference]; [[EXPExpectFBSnapshotTest instance] setReferenceImagesDirectory:referenceImagesDirectory]; }; @interface EXPExpect(ReferenceDirExtension) - (NSString *)_getDefaultReferenceDirectory; @end @implementation EXPExpect(ReferenceDirExtension) - (NSString *)_getDefaultReferenceDirectory { NSString *globalReference = [[EXPExpectFBSnapshotTest instance] referenceImagesDirectory]; if (globalReference) { return globalReference; } // Search the test file's path to find the first folder with the substring "tests" // then append "/ReferenceImages" and use that NSString *testFileName = [NSString stringWithCString:self.fileName encoding:NSUTF8StringEncoding]; NSArray *pathComponents = [testFileName pathComponents]; for (NSString *folder in pathComponents) { if ([folder.lowercaseString rangeOfString:@"tests"].location != NSNotFound) { NSArray *folderPathComponents = [pathComponents subarrayWithRange:NSMakeRange(0, [pathComponents indexOfObject:folder] + 1)]; return [NSString stringWithFormat:@"%@/ReferenceImages", [folderPathComponents componentsJoinedByString:@"/"]]; } } [NSException raise:@"Could not infer reference image folder" format:@"You should provide a reference dir using setGlobalReferenceImageDir(FB_REFERENCE_IMAGE_DIR);"]; return nil; } @end // If you're bringing in Speca via CocoaPods // use the test path to get the test's image file URL #if __has_include() #import #import #import NSString *sanitizedTestPath(); NSString *sanitizedTestPath(){ id compiledExample = [[NSThread currentThread] threadDictionary][@"SPTCurrentSpec"]; // SPTSpec NSString *name; if ([compiledExample respondsToSelector:@selector(name)]) { // Specta 0.3 syntax name = [compiledExample performSelector:@selector(name)]; } else if ([compiledExample respondsToSelector:@selector(fileName)]) { // Specta 0.2 syntax name = [compiledExample performSelector:@selector(fileName)]; } name = [[[[name componentsSeparatedByString:@" test_"] lastObject] stringByReplacingOccurrencesOfString:@"__" withString:@"_"] stringByReplacingOccurrencesOfString:@"]" withString:@""]; return name; } EXPMatcherImplementationBegin(haveValidSnapshot, (void)){ __block NSError *error = nil; match(^BOOL{ NSString *referenceImageDir = [self _getDefaultReferenceDirectory]; NSString *name = sanitizedTestPath(); if ([actual isKindOfClass:UIViewController.class]) { [actual beginAppearanceTransition:YES animated:NO]; [actual endAppearanceTransition]; actual = [actual view]; } return [EXPExpectFBSnapshotTest compareSnapshotOfViewOrLayer:actual snapshot:name testCase:[self testCase] record:NO referenceDirectory:referenceImageDir error:&error]; }); failureMessageForTo(^NSString *{ return [EXPExpectFBSnapshotTest combinedError:@"expected a matching snapshot in" test:sanitizedTestPath() error:error]; }); failureMessageForNotTo(^NSString *{ return [EXPExpectFBSnapshotTest combinedError:@"expected to not have a matching snapshot in" test:sanitizedTestPath() error:error]; }); } EXPMatcherImplementationEnd EXPMatcherImplementationBegin(recordSnapshot, (void)) { __block NSError *error = nil; BOOL actualIsViewLayerOrViewController = ([actual isKindOfClass:UIView.class] || [actual isKindOfClass:CALayer.class] || [actual isKindOfClass:UIViewController.class]); prerequisite(^BOOL{ return actualIsViewLayerOrViewController; }); match(^BOOL{ NSString *referenceImageDir = [self _getDefaultReferenceDirectory]; // For view controllers do the viewWill/viewDid dance, then pass view through if ([actual isKindOfClass:UIViewController.class]) { [actual beginAppearanceTransition:YES animated:NO]; [actual endAppearanceTransition]; actual = [actual view]; } [EXPExpectFBSnapshotTest compareSnapshotOfViewOrLayer:actual snapshot:sanitizedTestPath() testCase:[self testCase] record:YES referenceDirectory:referenceImageDir error:&error]; return NO; }); failureMessageForTo(^NSString *{ if (!actualIsViewLayerOrViewController) { return [EXPExpectFBSnapshotTest combinedError:@"Expected a View, Layer or View Controller." test:sanitizedTestPath() error:nil]; } if (error) { return [EXPExpectFBSnapshotTest combinedError:@"expected to record a snapshot in" test:sanitizedTestPath() error:error]; } else { return [NSString stringWithFormat:@"snapshot %@ successfully recorded, replace recordSnapshot with a check", sanitizedTestPath()]; } }); failureMessageForNotTo(^NSString *{ if (error) { return [EXPExpectFBSnapshotTest combinedError:@"expected to record a snapshot in" test:sanitizedTestPath() error:error]; } else { return [NSString stringWithFormat:@"snapshot %@ successfully recorded, replace recordSnapshot with a check", sanitizedTestPath()]; } }); } EXPMatcherImplementationEnd #else // If you don't have Speca stub the functions EXPMatcherImplementationBegin(haveValidSnapshot, (void)){ prerequisite(^BOOL{ return NO; }); failureMessageForTo(^NSString *{ return @"you need Specta installed via CocoaPods to use haveValidSnapshot, use haveValidSnapshotNamed instead"; }); failureMessageForNotTo(^NSString *{ return @"you need Specta installed via CocoaPods to use haveValidSnapshot, use haveValidSnapshotNamed instead"; }); } EXPMatcherImplementationEnd EXPMatcherImplementationBegin(recordSnapshot, (void)) { prerequisite(^BOOL{ return NO; }); failureMessageForTo(^NSString *{ return @"you need Specta installed via CocoaPods to use recordSnapshot, use recordSnapshotNamed instead"; }); failureMessageForNotTo(^NSString *{ return @"you need Specta installed via CocoaPods to use recordSnapshot, use recordSnapshotNamed instead"; }); } EXPMatcherImplementationEnd #endif EXPMatcherImplementationBegin(haveValidSnapshotNamed, (NSString *snapshot)){ BOOL snapshotIsNil = (snapshot == nil); __block NSError *error = nil; prerequisite(^BOOL{ return !(snapshotIsNil); }); match(^BOOL{ NSString *referenceImageDir = [self _getDefaultReferenceDirectory]; if ([actual isKindOfClass:UIViewController.class]) { [actual beginAppearanceTransition:YES animated:NO]; [actual endAppearanceTransition]; actual = [actual view]; } return [EXPExpectFBSnapshotTest compareSnapshotOfViewOrLayer:actual snapshot:snapshot testCase:[self testCase] record:NO referenceDirectory:referenceImageDir error:&error]; }); failureMessageForTo(^NSString *{ return [EXPExpectFBSnapshotTest combinedError:@"expected a matching snapshot named" test:snapshot error:error]; }); failureMessageForNotTo(^NSString *{ return [EXPExpectFBSnapshotTest combinedError:@"expected not to have a matching snapshot named" test:snapshot error:error]; }); } EXPMatcherImplementationEnd EXPMatcherImplementationBegin(recordSnapshotNamed, (NSString *snapshot)) { BOOL snapshotExists = (snapshot != nil); BOOL actualIsViewLayerOrViewController = ([actual isKindOfClass:UIView.class] || [actual isKindOfClass:CALayer.class] || [actual isKindOfClass:UIViewController.class]); __block NSError *error = nil; id actualRef = actual; prerequisite(^BOOL{ return actualRef && snapshotExists && actualIsViewLayerOrViewController; }); match(^BOOL{ NSString *referenceImageDir = [self _getDefaultReferenceDirectory]; // For view controllers do the viewWill/viewDid dance, then pass view through if ([actual isKindOfClass:UIViewController.class]) { [actual beginAppearanceTransition:YES animated:NO]; [actual endAppearanceTransition]; actual = [actual view]; } [EXPExpectFBSnapshotTest compareSnapshotOfViewOrLayer:actual snapshot:snapshot testCase:[self testCase] record:YES referenceDirectory:referenceImageDir error:&error]; return NO; }); failureMessageForTo(^NSString *{ if (!actualIsViewLayerOrViewController) { return [EXPExpectFBSnapshotTest combinedError:@"Expected a View, Layer or View Controller." test:snapshot error:nil]; } if (error) { return [EXPExpectFBSnapshotTest combinedError:@"expected to record a matching snapshot named" test:snapshot error:error]; } else { return [NSString stringWithFormat:@"snapshot %@ successfully recorded, replace recordSnapshot with a check", snapshot]; } }); failureMessageForNotTo(^NSString *{ if (!actualIsViewLayerOrViewController) { return [EXPExpectFBSnapshotTest combinedError:@"Expected a View, Layer or View Controller." test:snapshot error:nil]; } if (error) { return [EXPExpectFBSnapshotTest combinedError:@"expected to record a matching snapshot named" test:snapshot error:error]; } else { return [NSString stringWithFormat:@"snapshot %@ successfully recorded, replace recordSnapshot with a check", snapshot]; } }); } EXPMatcherImplementationEnd ================================================ FILE: Example/Pods/Expecta+Snapshots/LICENSE.md ================================================ MIT License Copyright (c) 2014 Daniel Doubrovkine, Artsy Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Example/Pods/Expecta+Snapshots/README.md ================================================ Expecta Matchers for FBSnapshotTestCase ======================================= [Expecta](https://github.com/specta/expecta) matchers for [ios-snapshot-test-case](https://github.com/facebook/ios-snapshot-test-case). [![Build Status](https://travis-ci.org/dblock/ios-snapshot-test-case-expecta.png)](https://travis-ci.org/dblock/ios-snapshot-test-case-expecta) ### Usage Add `Expecta+Snapshots` to your Podfile, the latest `FBSnapshotTestCase` will come in as a dependency. ``` ruby pod 'Expecta+Snapshots' ``` ### App setup Use `expect(view).to.recordSnapshotNamed(@"unique snapshot name")` to record a snapshot and `expect(view).to.haveValidSnapshotNamed(@"unique snapshot name")` to check it. If you project was compiled with Specta included, you have two extra methods that use the spec hierarchy to generate the snapshot name for you: `recordSnapshot()` and `haveValidSnapshot()`. You should only call these once per `it()` block. ``` Objective-C #define EXP_SHORTHAND #include #include #include #include "FBExampleView.h" SpecBegin(FBExampleView) describe(@"manual matching", ^{ it(@"matches view", ^{ FBExampleView *view = [[FBExampleView alloc] initWithFrame:CGRectMake(0, 0, 64, 64)]; expect(view).to.recordSnapshotNamed(@"FBExampleView"); expect(view).to.haveValidSnapshotNamed(@"FBExampleView"); }); it(@"doesn't match a view", ^{ FBExampleView *view = [[FBExampleView alloc] initWithFrame:CGRectMake(0, 0, 64, 64)]; expect(view).toNot.haveValidSnapshotNamed(@"FBExampleViewDoesNotExist"); }); }); describe(@"test name derived matching", ^{ it(@"matches view", ^{ FBExampleView *view = [[FBExampleView alloc] initWithFrame:CGRectMake(0, 0, 64, 64)]; expect(view).to.recordSnapshot(); expect(view).to.haveValidSnapshot(); }); it(@"doesn't match a view", ^{ FBExampleView *view = [[FBExampleView alloc] initWithFrame:CGRectMake(0, 0, 64, 64)]; expect(view).toNot.haveValidSnapshot(); }); }); SpecEnd ``` ### Sane defaults `EXPMatchers+FBSnapshotTest` will automatically figure out the tests folder, and [add a reference image](https://github.com/dblock/ios-snapshot-test-case-expecta/blob/master/EXPMatchers%2BFBSnapshotTest.m#L84-L85) directory, if you'd like to override this, you should include a `beforeAll` block setting the `setGlobalReferenceImageDir` in each file containing tests. ``` beforeAll(^{ setGlobalReferenceImageDir(FB_REFERENCE_IMAGE_DIR); }); ``` ### Example A complete project can be found in [FBSnapshotTestCaseDemo](FBSnapshotTestCaseDemo). Notably, take a look at [FBSnapshotTestCaseDemoSpecs.m](FBSnapshotTestCaseDemo/FBSnapshotTestCaseDemoTests/FBSnapshotTestCaseDemoSpecs.m) for a complete example, which is an expanded Specta version version of [FBSnapshotTestCaseDemoTests.m](https://github.com/facebook/ios-snapshot-test-case/blob/master/FBSnapshotTestCaseDemo/FBSnapshotTestCaseDemoTests/FBSnapshotTestCaseDemoTests.m). Finally you can consult the tests for [ARTiledImageView](https://github.com/dblock/ARTiledImageView/tree/master/IntegrationTests) or [NAMapKit](https://github.com/neilang/NAMapKit/tree/master/Demo/DemoTests). ### License MIT, see [LICENSE](LICENSE.md) ================================================ FILE: Example/Pods/FBSnapshotTestCase/FBSnapshotTestCase/FBSnapshotTestCase.h ================================================ /* * Copyright (c) 2013, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * */ #import #import #import #import #ifndef FB_REFERENCE_IMAGE_DIR #define FB_REFERENCE_IMAGE_DIR "\"$(SOURCE_ROOT)/$(PROJECT_NAME)Tests/ReferenceImages\"" #endif /** Similar to our much-loved XCTAssert() macros. Use this to perform your test. No need to write an explanation, though. @param view The view to snapshot @param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method. @param suffixes An NSOrderedSet of strings for the different suffixes */ #define FBSnapshotVerifyViewWithOptions(view__, identifier__, suffixes__) \ { \ NSError *error__ = nil; \ BOOL comparisonSuccess__; \ XCTAssertTrue((suffixes__.count > 0), @"Suffixes set cannot be empty %@", suffixes__); \ for (NSString *suffix__ in suffixes__) { \ NSString *referenceImagesDirectory__ = [NSString stringWithFormat:@"%s%@", FB_REFERENCE_IMAGE_DIR, suffix__]; \ comparisonSuccess__ = [self compareSnapshotOfView:(view__) referenceImagesDirectory:referenceImagesDirectory__ identifier:(identifier__) error:&error__]; \ if (comparisonSuccess__ || self.recordMode) break; \ } \ XCTAssertTrue(comparisonSuccess__, @"Snapshot comparison failed: %@", error__); \ XCTAssertFalse(self.recordMode, @"Test ran in record mode. Reference image is now saved. Disable record mode to perform an actual snapshot comparison!"); \ } #define FBSnapshotVerifyView(view__, identifier__) \ { \ FBSnapshotVerifyViewWithOptions(view__, identifier__, FBSnapshotTestCaseDefaultSuffixes()); \ } /** Similar to our much-loved XCTAssert() macros. Use this to perform your test. No need to write an explanation, though. @param layer The layer to snapshot @param identifier An optional identifier, used is there are multiple snapshot tests in a given -test method. @param suffixes An NSOrderedSet of strings for the different suffixes */ #define FBSnapshotVerifyLayerWithOptions(layer__, identifier__, suffixes__) \ { \ NSError *error__ = nil; \ BOOL comparisonSuccess__; \ XCTAssertTrue((suffixes__.count > 0), @"Suffixes set cannot be empty %@", suffixes__); \ for (NSString *suffix__ in suffixes__) { \ NSString *referenceImagesDirectory__ = [NSString stringWithFormat:@"%s%@", FB_REFERENCE_IMAGE_DIR, suffix__]; \ comparisonSuccess__ = [self compareSnapshotOfLayer:(layer__) referenceImagesDirectory:referenceImagesDirectory__ identifier:(identifier__) error:&error__]; \ if (comparisonSuccess__ || self.recordMode) break; \ } \ XCTAssertTrue(comparisonSuccess__, @"Snapshot comparison failed: %@", error__); \ XCTAssertFalse(self.recordMode, @"Test ran in record mode. Reference image is now saved. Disable record mode to perform an actual snapshot comparison!"); \ } #define FBSnapshotVerifyLayer(layer__, identifier__) \ { \ FBSnapshotVerifyLayerWithOptions(layer__, identifier__, FBSnapshotTestCaseDefaultSuffixes()); \ } /** The base class of view snapshotting tests. If you have small UI component, it's often easier to configure it in a test and compare an image of the view to a reference image that write lots of complex layout-code tests. In order to flip the tests in your subclass to record the reference images set @c recordMode to @c YES. For example: @code - (void)setUp { [super setUp]; self.recordMode = YES; } @endcode */ @interface FBSnapshotTestCase : XCTestCase /** When YES, the test macros will save reference images, rather than performing an actual test. */ @property (readwrite, nonatomic, assign) BOOL recordMode; /** Performs the comparison or records a snapshot of the layer if recordMode is YES. @param layer The Layer to snapshot @param referenceImagesDirectory The directory in which reference images are stored. @param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method. @param errorPtr An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc). @returns YES if the comparison (or saving of the reference image) succeeded. */ - (BOOL)compareSnapshotOfLayer:(CALayer *)layer referenceImagesDirectory:(NSString *)referenceImagesDirectory identifier:(NSString *)identifier error:(NSError **)errorPtr; /** Performs the comparison or records a snapshot of the view if recordMode is YES. @param view The view to snapshot @param referenceImagesDirectory The directory in which reference images are stored. @param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method. @param errorPtr An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc). @returns YES if the comparison (or saving of the reference image) succeeded. */ - (BOOL)compareSnapshotOfView:(UIView *)view referenceImagesDirectory:(NSString *)referenceImagesDirectory identifier:(NSString *)identifier error:(NSError **)errorPtr; @end ================================================ FILE: Example/Pods/FBSnapshotTestCase/FBSnapshotTestCase/FBSnapshotTestCase.m ================================================ /* * Copyright (c) 2013, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * */ #import "FBSnapshotTestCase.h" #import "FBSnapshotTestController.h" @implementation FBSnapshotTestCase { FBSnapshotTestController *_snapshotController; } - (void)setUp { [super setUp]; _snapshotController = [[FBSnapshotTestController alloc] initWithTestName:NSStringFromClass([self class])]; } - (void)tearDown { _snapshotController = nil; [super tearDown]; } - (BOOL)recordMode { return _snapshotController.recordMode; } - (void)setRecordMode:(BOOL)recordMode { NSAssert1(_snapshotController, @"%s cannot be called before [super setUp]", __FUNCTION__); _snapshotController.recordMode = recordMode; } - (BOOL)compareSnapshotOfLayer:(CALayer *)layer referenceImagesDirectory:(NSString *)referenceImagesDirectory identifier:(NSString *)identifier error:(NSError **)errorPtr { return [self _compareSnapshotOfViewOrLayer:layer referenceImagesDirectory:referenceImagesDirectory identifier:identifier error:errorPtr]; } - (BOOL)compareSnapshotOfView:(UIView *)view referenceImagesDirectory:(NSString *)referenceImagesDirectory identifier:(NSString *)identifier error:(NSError **)errorPtr { return [self _compareSnapshotOfViewOrLayer:view referenceImagesDirectory:referenceImagesDirectory identifier:identifier error:errorPtr]; } #pragma mark - #pragma mark Private API - (BOOL)_compareSnapshotOfViewOrLayer:(id)viewOrLayer referenceImagesDirectory:(NSString *)referenceImagesDirectory identifier:(NSString *)identifier error:(NSError **)errorPtr { _snapshotController.referenceImagesDirectory = referenceImagesDirectory; return [_snapshotController compareSnapshotOfViewOrLayer:viewOrLayer selector:self.invocation.selector identifier:identifier error:errorPtr]; } @end ================================================ FILE: Example/Pods/FBSnapshotTestCase/FBSnapshotTestCase/FBSnapshotTestCasePlatform.h ================================================ /* * Copyright (c) 2015, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * */ #import #ifdef __cplusplus extern "C" { #endif /** Returns a Boolean value that indicates whether the snapshot test is running in 64Bit. This method is a convenience for creating the suffixes set based on the architecture that the test is running. @returns @c YES if the test is running in 64bit, otherwise @c NO. */ BOOL FBSnapshotTestCaseIs64Bit(void); /** Returns a default set of strings that is used to append a suffix based on the architectures. @warning Do not modify this function, you can create your own and use it with @c FBSnapshotVerifyViewWithOptions() @returns An @c NSOrderedSet object containing strings that are appended to the reference images directory. */ NSOrderedSet *FBSnapshotTestCaseDefaultSuffixes(void); #ifdef __cplusplus } #endif ================================================ FILE: Example/Pods/FBSnapshotTestCase/FBSnapshotTestCase/FBSnapshotTestCasePlatform.m ================================================ /* * Copyright (c) 2015, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * */ #import BOOL FBSnapshotTestCaseIs64Bit(void) { #if __LP64__ return YES; #else return NO; #endif } NSOrderedSet *FBSnapshotTestCaseDefaultSuffixes(void) { NSMutableOrderedSet *suffixesSet = [[NSMutableOrderedSet alloc] init]; [suffixesSet addObject:@"_32"]; [suffixesSet addObject:@"_64"]; if (FBSnapshotTestCaseIs64Bit()) { return [suffixesSet reversedOrderedSet]; } return [suffixesSet copy]; } ================================================ FILE: Example/Pods/FBSnapshotTestCase/FBSnapshotTestCase/FBSnapshotTestController.h ================================================ /* * Copyright (c) 2013, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * */ #import #import typedef NS_ENUM(NSInteger, FBSnapshotTestControllerErrorCode) { FBSnapshotTestControllerErrorCodeUnknown, FBSnapshotTestControllerErrorCodeNeedsRecord, FBSnapshotTestControllerErrorCodePNGCreationFailed, FBSnapshotTestControllerErrorCodeImagesDifferentSizes, FBSnapshotTestControllerErrorCodeImagesDifferent, }; /** Errors returned by the methods of FBSnapshotTestController use this domain. */ extern NSString *const FBSnapshotTestControllerErrorDomain; /** Errors returned by the methods of FBSnapshotTestController sometimes contain this key in the `userInfo` dictionary. */ extern NSString *const FBReferenceImageFilePathKey; /** Provides the heavy-lifting for FBSnapshotTestCase. It loads and saves images, along with performing the actual pixel- by-pixel comparison of images. Instances are initialized with the test class, and directories to read and write to. */ @interface FBSnapshotTestController : NSObject /** Record snapshots. **/ @property (readwrite, nonatomic, assign) BOOL recordMode; /** @param testClass The subclass of FBSnapshotTestCase that is using this controller. @returns An instance of FBSnapshotTestController. */ - (instancetype)initWithTestClass:(Class)testClass; /** Designated initializer. @param testName The name of the tests. @returns An instance of FBSnapshotTestController. */ - (instancetype)initWithTestName:(NSString *)testName; /** Performs the comparison of the layer. @param layer The Layer to snapshot. @param selector The test method being run. @param identifier An optional identifier, used is there are muliptle snapshot tests in a given -test method. @param error An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc). @returns YES if the comparison (or saving of the reference image) succeeded. */ - (BOOL)compareSnapshotOfLayer:(CALayer *)layer selector:(SEL)selector identifier:(NSString *)identifier error:(NSError **)errorPtr; /** Performs the comparison of the view. @param view The view to snapshot. @param selector The test method being run. @param identifier An optional identifier, used is there are muliptle snapshot tests in a given -test method. @param error An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc). @returns YES if the comparison (or saving of the reference image) succeeded. */ - (BOOL)compareSnapshotOfView:(UIView *)view selector:(SEL)selector identifier:(NSString *)identifier error:(NSError **)errorPtr; /** Performs the comparison of a view or layer. @param view The view or layer to snapshot. @param selector The test method being run. @param identifier An optional identifier, used is there are muliptle snapshot tests in a given -test method. @param error An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc). @returns YES if the comparison (or saving of the reference image) succeeded. */ - (BOOL)compareSnapshotOfViewOrLayer:(id)viewOrLayer selector:(SEL)selector identifier:(NSString *)identifier error:(NSError **)errorPtr; /** The directory in which referfence images are stored. */ @property (readwrite, nonatomic, copy) NSString *referenceImagesDirectory; /** Loads a reference image. @param selector The test method being run. @param identifier The optional identifier, used when multiple images are tested in a single -test method. @param errorPtr An error, if this methods returns nil, the error will be something useful. @returns An image. */ - (UIImage *)referenceImageForSelector:(SEL)selector identifier:(NSString *)identifier error:(NSError **)errorPtr; /** Saves a reference image. @param selector The test method being run. @param identifier The optional identifier, used when multiple images are tested in a single -test method. @param errorPtr An error, if this methods returns NO, the error will be something useful. @returns An image. */ - (BOOL)saveReferenceImage:(UIImage *)image selector:(SEL)selector identifier:(NSString *)identifier error:(NSError **)errorPtr; /** Performs a pixel-by-pixel comparison of the two images. @param referenceImage The reference (correct) image. @param image The image to test against the reference. @param errorPtr An error that indicates why the comparison failed if it does. @returns YES if the comparison succeeded and the images are the same. */ - (BOOL)compareReferenceImage:(UIImage *)referenceImage toImage:(UIImage *)image error:(NSError **)errorPtr; /** Saves the reference image and the test image to `failedOutputDirectory`. @param referenceImage The reference (correct) image. @param testImage The image to test against the reference. @param selector The test method being run. @param identifier The optional identifier, used when multiple images are tested in a single -test method. @param errorPtr An error that indicates why the comparison failed if it does. @returns YES if the save succeeded. */ - (BOOL)saveFailedReferenceImage:(UIImage *)referenceImage testImage:(UIImage *)testImage selector:(SEL)selector identifier:(NSString *)identifier error:(NSError **)errorPtr; @end ================================================ FILE: Example/Pods/FBSnapshotTestCase/FBSnapshotTestCase/FBSnapshotTestController.m ================================================ /* * Copyright (c) 2013, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * */ #import "FBSnapshotTestController.h" #import "UIImage+Compare.h" #import "UIImage+Diff.h" #import NSString *const FBSnapshotTestControllerErrorDomain = @"FBSnapshotTestControllerErrorDomain"; NSString *const FBReferenceImageFilePathKey = @"FBReferenceImageFilePathKey"; @implementation FBSnapshotTestController { NSString *_testName; NSFileManager *_fileManager; } #pragma mark - #pragma mark Lifecycle - (instancetype)initWithTestClass:(Class)testClass; { return [self initWithTestName:NSStringFromClass(testClass)]; } - (instancetype)initWithTestName:(NSString *)testName { if ((self = [super init])) { _testName = [testName copy]; _fileManager = [[NSFileManager alloc] init]; } return self; } #pragma mark - #pragma mark Properties - (NSString *)description { return [NSString stringWithFormat:@"%@ %@", [super description], _referenceImagesDirectory]; } #pragma mark - #pragma mark Public API - (UIImage *)referenceImageForSelector:(SEL)selector identifier:(NSString *)identifier error:(NSError **)errorPtr { NSString *filePath = [self _referenceFilePathForSelector:selector identifier:identifier]; UIImage *image = [UIImage imageWithContentsOfFile:filePath]; if (nil == image && NULL != errorPtr) { BOOL exists = [_fileManager fileExistsAtPath:filePath]; if (!exists) { *errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain code:FBSnapshotTestControllerErrorCodeNeedsRecord userInfo:@{ FBReferenceImageFilePathKey: filePath, NSLocalizedDescriptionKey: @"Unable to load reference image.", NSLocalizedFailureReasonErrorKey: @"Reference image not found. You need to run the test in record mode", }]; } else { *errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain code:FBSnapshotTestControllerErrorCodeUnknown userInfo:nil]; } } return image; } - (BOOL)saveReferenceImage:(UIImage *)image selector:(SEL)selector identifier:(NSString *)identifier error:(NSError **)errorPtr { BOOL didWrite = NO; if (nil != image) { NSString *filePath = [self _referenceFilePathForSelector:selector identifier:identifier]; NSData *pngData = UIImagePNGRepresentation(image); if (nil != pngData) { NSError *creationError = nil; BOOL didCreateDir = [_fileManager createDirectoryAtPath:[filePath stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:&creationError]; if (!didCreateDir) { if (NULL != errorPtr) { *errorPtr = creationError; } return NO; } didWrite = [pngData writeToFile:filePath options:NSDataWritingAtomic error:errorPtr]; if (didWrite) { NSLog(@"Reference image save at: %@", filePath); } } else { if (nil != errorPtr) { *errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain code:FBSnapshotTestControllerErrorCodePNGCreationFailed userInfo:@{ FBReferenceImageFilePathKey: filePath, }]; } } } return didWrite; } - (BOOL)saveFailedReferenceImage:(UIImage *)referenceImage testImage:(UIImage *)testImage selector:(SEL)selector identifier:(NSString *)identifier error:(NSError **)errorPtr { NSData *referencePNGData = UIImagePNGRepresentation(referenceImage); NSData *testPNGData = UIImagePNGRepresentation(testImage); NSString *referencePath = [self _failedFilePathForSelector:selector identifier:identifier fileNameType:FBTestSnapshotFileNameTypeFailedReference]; NSError *creationError = nil; BOOL didCreateDir = [_fileManager createDirectoryAtPath:[referencePath stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:&creationError]; if (!didCreateDir) { if (NULL != errorPtr) { *errorPtr = creationError; } return NO; } if (![referencePNGData writeToFile:referencePath options:NSDataWritingAtomic error:errorPtr]) { return NO; } NSString *testPath = [self _failedFilePathForSelector:selector identifier:identifier fileNameType:FBTestSnapshotFileNameTypeFailedTest]; if (![testPNGData writeToFile:testPath options:NSDataWritingAtomic error:errorPtr]) { return NO; } NSString *diffPath = [self _failedFilePathForSelector:selector identifier:identifier fileNameType:FBTestSnapshotFileNameTypeFailedTestDiff]; UIImage *diffImage = [referenceImage diffWithImage:testImage]; NSData *diffImageData = UIImagePNGRepresentation(diffImage); if (![diffImageData writeToFile:diffPath options:NSDataWritingAtomic error:errorPtr]) { return NO; } NSLog(@"If you have Kaleidoscope installed you can run this command to see an image diff:\n" @"ksdiff \"%@\" \"%@\"", referencePath, testPath); return YES; } - (BOOL)compareReferenceImage:(UIImage *)referenceImage toImage:(UIImage *)image error:(NSError **)errorPtr { if (CGSizeEqualToSize(referenceImage.size, image.size)) { BOOL imagesEqual = [referenceImage compareWithImage:image]; if (NULL != errorPtr) { *errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain code:FBSnapshotTestControllerErrorCodeImagesDifferent userInfo:@{ NSLocalizedDescriptionKey: @"Images different", }]; } return imagesEqual; } if (NULL != errorPtr) { *errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain code:FBSnapshotTestControllerErrorCodeImagesDifferentSizes userInfo:@{ NSLocalizedDescriptionKey: @"Images different sizes", NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"referenceImage:%@, image:%@", NSStringFromCGSize(referenceImage.size), NSStringFromCGSize(image.size)], }]; } return NO; } #pragma mark - #pragma mark Private API typedef NS_ENUM(NSUInteger, FBTestSnapshotFileNameType) { FBTestSnapshotFileNameTypeReference, FBTestSnapshotFileNameTypeFailedReference, FBTestSnapshotFileNameTypeFailedTest, FBTestSnapshotFileNameTypeFailedTestDiff, }; - (NSString *)_fileNameForSelector:(SEL)selector identifier:(NSString *)identifier fileNameType:(FBTestSnapshotFileNameType)fileNameType { NSString *fileName = nil; switch (fileNameType) { case FBTestSnapshotFileNameTypeFailedReference: fileName = @"reference_"; break; case FBTestSnapshotFileNameTypeFailedTest: fileName = @"failed_"; break; case FBTestSnapshotFileNameTypeFailedTestDiff: fileName = @"diff_"; break; default: fileName = @""; break; } fileName = [fileName stringByAppendingString:NSStringFromSelector(selector)]; if (0 < identifier.length) { fileName = [fileName stringByAppendingFormat:@"_%@", identifier]; } if ([[UIScreen mainScreen] scale] > 1) { fileName = [fileName stringByAppendingFormat:@"@%.fx", [[UIScreen mainScreen] scale]]; } fileName = [fileName stringByAppendingPathExtension:@"png"]; return fileName; } - (NSString *)_referenceFilePathForSelector:(SEL)selector identifier:(NSString *)identifier { NSString *fileName = [self _fileNameForSelector:selector identifier:identifier fileNameType:FBTestSnapshotFileNameTypeReference]; NSString *filePath = [_referenceImagesDirectory stringByAppendingPathComponent:_testName]; filePath = [filePath stringByAppendingPathComponent:fileName]; return filePath; } - (NSString *)_failedFilePathForSelector:(SEL)selector identifier:(NSString *)identifier fileNameType:(FBTestSnapshotFileNameType)fileNameType { NSString *fileName = [self _fileNameForSelector:selector identifier:identifier fileNameType:fileNameType]; NSString *folderPath = NSTemporaryDirectory(); if (getenv("IMAGE_DIFF_DIR")) { folderPath = @(getenv("IMAGE_DIFF_DIR")); } NSString *filePath = [folderPath stringByAppendingPathComponent:_testName]; filePath = [filePath stringByAppendingPathComponent:fileName]; return filePath; } - (BOOL)compareSnapshotOfLayer:(CALayer *)layer selector:(SEL)selector identifier:(NSString *)identifier error:(NSError **)errorPtr { return [self compareSnapshotOfViewOrLayer:layer selector:selector identifier:identifier error:errorPtr]; } - (BOOL)compareSnapshotOfView:(UIView *)view selector:(SEL)selector identifier:(NSString *)identifier error:(NSError **)errorPtr { return [self compareSnapshotOfViewOrLayer:view selector:selector identifier:identifier error:errorPtr]; } - (BOOL)compareSnapshotOfViewOrLayer:(id)viewOrLayer selector:(SEL)selector identifier:(NSString *)identifier error:(NSError **)errorPtr { if (self.recordMode) { return [self _recordSnapshotOfViewOrLayer:viewOrLayer selector:selector identifier:identifier error:errorPtr]; } else { return [self _performPixelComparisonWithViewOrLayer:viewOrLayer selector:selector identifier:identifier error:errorPtr]; } } #pragma mark - #pragma mark Private API - (BOOL)_performPixelComparisonWithViewOrLayer:(UIView *)viewOrLayer selector:(SEL)selector identifier:(NSString *)identifier error:(NSError **)errorPtr { UIImage *referenceImage = [self referenceImageForSelector:selector identifier:identifier error:errorPtr]; if (nil != referenceImage) { UIImage *snapshot = [self _snapshotViewOrLayer:viewOrLayer]; BOOL imagesSame = [self compareReferenceImage:referenceImage toImage:snapshot error:errorPtr]; if (!imagesSame) { [self saveFailedReferenceImage:referenceImage testImage:snapshot selector:selector identifier:identifier error:errorPtr]; } return imagesSame; } return NO; } - (BOOL)_recordSnapshotOfViewOrLayer:(id)viewOrLayer selector:(SEL)selector identifier:(NSString *)identifier error:(NSError **)errorPtr { UIImage *snapshot = [self _snapshotViewOrLayer:viewOrLayer]; return [self saveReferenceImage:snapshot selector:selector identifier:identifier error:errorPtr]; } - (UIImage *)_snapshotViewOrLayer:(id)viewOrLayer { CALayer *layer = nil; if ([viewOrLayer isKindOfClass:[UIView class]]) { return [self _renderView:viewOrLayer]; } else if ([viewOrLayer isKindOfClass:[CALayer class]]) { layer = (CALayer *)viewOrLayer; [layer layoutIfNeeded]; return [self _renderLayer:layer]; } else { [NSException raise:@"Only UIView and CALayer classes can be snapshotted" format:@"%@", viewOrLayer]; } return nil; } - (UIImage *)_renderLayer:(CALayer *)layer { CGRect bounds = layer.bounds; NSAssert1(CGRectGetWidth(bounds), @"Zero width for layer %@", layer); NSAssert1(CGRectGetHeight(bounds), @"Zero height for layer %@", layer); UIGraphicsBeginImageContextWithOptions(bounds.size, NO, 0); CGContextRef context = UIGraphicsGetCurrentContext(); NSAssert1(context, @"Could not generate context for layer %@", layer); CGContextSaveGState(context); { [layer renderInContext:context]; } CGContextRestoreGState(context); UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return snapshot; } - (UIImage *)_renderView:(UIView *)view { [view layoutIfNeeded]; return [self _renderLayer:view.layer]; } @end ================================================ FILE: Example/Pods/FBSnapshotTestCase/FBSnapshotTestCase/UIImage+Compare.h ================================================ // // Created by Gabriel Handford on 3/1/09. // Copyright 2009-2013. All rights reserved. // Created by John Boiles on 10/20/11. // Copyright (c) 2011. All rights reserved // Modified by Felix Schulze on 2/11/13. // Copyright 2013. All rights reserved. // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without // restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following // conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // #import @interface UIImage (Compare) - (BOOL)compareWithImage:(UIImage *)image; @end ================================================ FILE: Example/Pods/FBSnapshotTestCase/FBSnapshotTestCase/UIImage+Compare.m ================================================ // // Created by Gabriel Handford on 3/1/09. // Copyright 2009-2013. All rights reserved. // Created by John Boiles on 10/20/11. // Copyright (c) 2011. All rights reserved // Modified by Felix Schulze on 2/11/13. // Copyright 2013. All rights reserved. // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without // restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following // conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // #import "UIImage+Compare.h" @implementation UIImage (Compare) - (BOOL)compareWithImage:(UIImage *)image { NSAssert(CGSizeEqualToSize(self.size, image.size), @"Images must be same size."); // The images have the equal size, so we could use the smallest amount of bytes because of byte padding size_t minBytesPerRow = MIN(CGImageGetBytesPerRow(self.CGImage), CGImageGetBytesPerRow(image.CGImage)); size_t referenceImageSizeBytes = CGImageGetHeight(self.CGImage) * minBytesPerRow; void *referenceImagePixels = calloc(1, referenceImageSizeBytes); void *imagePixels = calloc(1, referenceImageSizeBytes); if (!referenceImagePixels || !imagePixels) { free(referenceImagePixels); free(imagePixels); return NO; } CGContextRef referenceImageContext = CGBitmapContextCreate(referenceImagePixels, CGImageGetWidth(self.CGImage), CGImageGetHeight(self.CGImage), CGImageGetBitsPerComponent(self.CGImage), minBytesPerRow, CGImageGetColorSpace(self.CGImage), (CGBitmapInfo)kCGImageAlphaPremultipliedLast ); CGContextRef imageContext = CGBitmapContextCreate(imagePixels, CGImageGetWidth(image.CGImage), CGImageGetHeight(image.CGImage), CGImageGetBitsPerComponent(image.CGImage), minBytesPerRow, CGImageGetColorSpace(image.CGImage), (CGBitmapInfo)kCGImageAlphaPremultipliedLast ); CGFloat scaleFactor = [[UIScreen mainScreen] scale]; CGContextScaleCTM(referenceImageContext, scaleFactor, scaleFactor); CGContextScaleCTM(imageContext, scaleFactor, scaleFactor); if (!referenceImageContext || !imageContext) { CGContextRelease(referenceImageContext); CGContextRelease(imageContext); free(referenceImagePixels); free(imagePixels); return NO; } CGContextDrawImage(referenceImageContext, CGRectMake(0, 0, self.size.width, self.size.height), self.CGImage); CGContextDrawImage(imageContext, CGRectMake(0, 0, image.size.width, image.size.height), image.CGImage); CGContextRelease(referenceImageContext); CGContextRelease(imageContext); BOOL imageEqual = (memcmp(referenceImagePixels, imagePixels, referenceImageSizeBytes) == 0); free(referenceImagePixels); free(imagePixels); return imageEqual; } @end ================================================ FILE: Example/Pods/FBSnapshotTestCase/FBSnapshotTestCase/UIImage+Diff.h ================================================ // // Created by Gabriel Handford on 3/1/09. // Copyright 2009-2013. All rights reserved. // Created by John Boiles on 10/20/11. // Copyright (c) 2011. All rights reserved // Modified by Felix Schulze on 2/11/13. // Copyright 2013. All rights reserved. // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without // restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following // conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // #import @interface UIImage (Diff) - (UIImage *)diffWithImage:(UIImage *)image; @end ================================================ FILE: Example/Pods/FBSnapshotTestCase/FBSnapshotTestCase/UIImage+Diff.m ================================================ // // Created by Gabriel Handford on 3/1/09. // Copyright 2009-2013. All rights reserved. // Created by John Boiles on 10/20/11. // Copyright (c) 2011. All rights reserved // Modified by Felix Schulze on 2/11/13. // Copyright 2013. All rights reserved. // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without // restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following // conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // #import "UIImage+Diff.h" @implementation UIImage (Diff) - (UIImage *)diffWithImage:(UIImage *)image { if (!image) { return nil; } CGSize imageSize = CGSizeMake(MAX(self.size.width, image.size.width), MAX(self.size.height, image.size.height)); UIGraphicsBeginImageContextWithOptions(imageSize, YES, 0); CGContextRef context = UIGraphicsGetCurrentContext(); [self drawInRect:CGRectMake(0, 0, self.size.width, self.size.height)]; CGContextSetAlpha(context, 0.5); CGContextBeginTransparencyLayer(context, NULL); [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)]; CGContextSetBlendMode(context, kCGBlendModeDifference); CGContextSetFillColorWithColor(context,[UIColor whiteColor].CGColor); CGContextFillRect(context, CGRectMake(0, 0, self.size.width, self.size.height)); CGContextEndTransparencyLayer(context); UIImage *returnImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return returnImage; } @end ================================================ FILE: Example/Pods/FBSnapshotTestCase/LICENSE ================================================ BSD License For the FBSnapshotTestCase software Copyright (c) 2013, Facebook, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name Facebook nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: Example/Pods/FBSnapshotTestCase/README.md ================================================ FBSnapshotTestCase ====================== [![Build Status](https://travis-ci.org/facebook/ios-snapshot-test-case.svg)](https://travis-ci.org/facebook/ios-snapshot-test-case) What it does ------------ A "snapshot test case" takes a configured `UIView` or `CALayer` and uses the `renderInContext:` method to get an image snapshot of its contents. It compares this snapshot to a "reference image" stored in your source code repository and fails the test if the two images don't match. Why? ---- At Facebook we write a lot of UI code. As you might imagine, each type of feed story is rendered using a subclass of `UIView`. There are a lot of edge cases that we want to handle correctly: - What if there is more text than can fit in the space available? - What if an image doesn't match the size of an image view? - What should the highlighted state look like? It's straightforward to test logic code, but less obvious how you should test views. You can do a lot of rectangle asserts, but these are hard to understand or visualize. Looking at an image diff shows you exactly what changed and how it will look to users. We developed `FBSnapshotTestCase` to make snapshot tests easy. Installation with CocoaPods --------------------------- 1. Add the following lines to your Podfile: ``` target "Tests" do pod 'FBSnapshotTestCase' end ``` Replace "Tests" with the name of your test project. 2. Define `FB_REFERENCE_IMAGE_DIR` in `GCC_PREPROCESSOR_DEFINITIONS`. This should point to the directory where you want reference images to be stored. At Facebook, we normally use this: `GCC_PREPROCESSOR_DEFINITIONS = $(inherited) FB_REFERENCE_IMAGE_DIR="\"$(SOURCE_ROOT)/$(PROJECT_NAME)Tests/ReferenceImages\""` Creating a snapshot test ------------------------ 1. Subclass `FBSnapshotTestCase` instead of `XCTestCase`. 2. From within your test, use `FBSnapshotVerifyView`. 3. Run the test once with `self.recordMode = YES;` in the test's `-setUp` method. (This creates the reference images on disk.) 4. Remove the line enabling record mode and run the test. Features -------- - Automatically names reference images on disk according to test class and selector. - Prints a descriptive error message to the console on failure. (Bonus: failure message includes a one-line command to see an image diff if you have [Kaleidoscope](http://www.kaleidoscopeapp.com) installed.) - Supply an optional "identifier" if you want to perform multiple snapshots in a single test method. - Support for `CALayer` via `FBSnapshotVerifyLayer`. Notes ----- Your unit test must be an "application test", not a "logic test." (That is, it must be run within the Simulator so that it has access to UIKit.) In Xcode 5 and later new projects only offer application tests, but older projects will have separate targets for the two types. Authors ------- `FBSnapshotTestCase` was written at Facebook by [Jonathan Dann](https://facebook.com/j.p.dann) with significant contributions by [Todd Krabach](https://facebook.com/toddkrabach). License ------- `FBSnapshotTestCase` is BSD-licensed. See `LICENSE`. ================================================ FILE: Example/Pods/Local Podspecs/MWPhotoBrowser.podspec.json ================================================ { "name": "MWPhotoBrowser", "version": "2.1.2", "license": "MIT", "summary": "A simple iOS photo and video browser with optional grid view, captions and selections.", "description": "MWPhotoBrowser can display one or more images or videos by providing either UIImage\nobjects, PHAsset objects, or URLs to library assets, web images/videos or local files.\nThe photo browser handles the downloading and caching of photos from the web seamlessly.\nPhotos can be zoomed and panned, and optional (customisable) captions can be displayed.", "screenshots": [ "https://raw.github.com/mwaterfall/MWPhotoBrowser/master/Screenshots/MWPhotoBrowser1.png", "https://raw.github.com/mwaterfall/MWPhotoBrowser/master/Screenshots/MWPhotoBrowser2.png", "https://raw.github.com/mwaterfall/MWPhotoBrowser/master/Screenshots/MWPhotoBrowser3.png", "https://raw.github.com/mwaterfall/MWPhotoBrowser/master/Screenshots/MWPhotoBrowser4.png", "https://raw.github.com/mwaterfall/MWPhotoBrowser/master/Screenshots/MWPhotoBrowser5.png", "https://raw.github.com/mwaterfall/MWPhotoBrowser/master/Screenshots/MWPhotoBrowser6.png" ], "homepage": "https://github.com/mwaterfall/MWPhotoBrowser", "authors": { "Michael Waterfall": "michaelwaterfall@gmail.com" }, "social_media_url": "https://twitter.com/mwaterfall", "source": { "git": "https://github.com/mwaterfall/MWPhotoBrowser.git", "tag": "2.1.2" }, "platforms": { "ios": "7.0" }, "source_files": "Pod/Classes/**/*", "resource_bundles": { "MWPhotoBrowser": [ "Pod/Assets/*.png" ] }, "requires_arc": true, "frameworks": [ "ImageIO", "QuartzCore", "AssetsLibrary", "MediaPlayer" ], "weak_frameworks": "Photos", "dependencies": { "MBProgressHUD": [ "~> 0.9" ], "DACircularProgress": [ "~> 2.3" ], "SDWebImage": [ "~> 3.7", "!= 3.7.2" ] } } ================================================ FILE: Example/Pods/MBProgressHUD/LICENSE ================================================ Copyright (c) 2009-2015 Matej Bukovinski Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Example/Pods/MBProgressHUD/MBProgressHUD.h ================================================ // // MBProgressHUD.h // Version 0.9.2 // Created by Matej Bukovinski on 2.4.09. // // This code is distributed under the terms and conditions of the MIT license. // Copyright (c) 2009-2015 Matej Bukovinski // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import #import #import @protocol MBProgressHUDDelegate; typedef NS_ENUM(NSInteger, MBProgressHUDMode) { /** Progress is shown using an UIActivityIndicatorView. This is the default. */ MBProgressHUDModeIndeterminate, /** Progress is shown using a round, pie-chart like, progress view. */ MBProgressHUDModeDeterminate, /** Progress is shown using a horizontal progress bar */ MBProgressHUDModeDeterminateHorizontalBar, /** Progress is shown using a ring-shaped progress view. */ MBProgressHUDModeAnnularDeterminate, /** Shows a custom view */ MBProgressHUDModeCustomView, /** Shows only labels */ MBProgressHUDModeText }; typedef NS_ENUM(NSInteger, MBProgressHUDAnimation) { /** Opacity animation */ MBProgressHUDAnimationFade, /** Opacity + scale animation */ MBProgressHUDAnimationZoom, MBProgressHUDAnimationZoomOut = MBProgressHUDAnimationZoom, MBProgressHUDAnimationZoomIn }; #ifndef MB_INSTANCETYPE #if __has_feature(objc_instancetype) #define MB_INSTANCETYPE instancetype #else #define MB_INSTANCETYPE id #endif #endif #ifndef MB_STRONG #if __has_feature(objc_arc) #define MB_STRONG strong #else #define MB_STRONG retain #endif #endif #ifndef MB_WEAK #if __has_feature(objc_arc_weak) #define MB_WEAK weak #elif __has_feature(objc_arc) #define MB_WEAK unsafe_unretained #else #define MB_WEAK assign #endif #endif #if NS_BLOCKS_AVAILABLE typedef void (^MBProgressHUDCompletionBlock)(); #endif /** * Displays a simple HUD window containing a progress indicator and two optional labels for short messages. * * This is a simple drop-in class for displaying a progress HUD view similar to Apple's private UIProgressHUD class. * The MBProgressHUD window spans over the entire space given to it by the initWithFrame constructor and catches all * user input on this region, thereby preventing the user operations on components below the view. The HUD itself is * drawn centered as a rounded semi-transparent view which resizes depending on the user specified content. * * This view supports four modes of operation: * - MBProgressHUDModeIndeterminate - shows a UIActivityIndicatorView * - MBProgressHUDModeDeterminate - shows a custom round progress indicator * - MBProgressHUDModeAnnularDeterminate - shows a custom annular progress indicator * - MBProgressHUDModeCustomView - shows an arbitrary, user specified view (see `customView`) * * All three modes can have optional labels assigned: * - If the labelText property is set and non-empty then a label containing the provided content is placed below the * indicator view. * - If also the detailsLabelText property is set then another label is placed below the first label. */ @interface MBProgressHUD : UIView /** * Creates a new HUD, adds it to provided view and shows it. The counterpart to this method is hideHUDForView:animated:. * * @note This method sets `removeFromSuperViewOnHide`. The HUD will automatically be removed from the view hierarchy when hidden. * * @param view The view that the HUD will be added to * @param animated If set to YES the HUD will appear using the current animationType. If set to NO the HUD will not use * animations while appearing. * @return A reference to the created HUD. * * @see hideHUDForView:animated: * @see animationType */ + (MB_INSTANCETYPE)showHUDAddedTo:(UIView *)view animated:(BOOL)animated; /** * Finds the top-most HUD subview and hides it. The counterpart to this method is showHUDAddedTo:animated:. * * @note This method sets `removeFromSuperViewOnHide`. The HUD will automatically be removed from the view hierarchy when hidden. * * @param view The view that is going to be searched for a HUD subview. * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use * animations while disappearing. * @return YES if a HUD was found and removed, NO otherwise. * * @see showHUDAddedTo:animated: * @see animationType */ + (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated; /** * Finds all the HUD subviews and hides them. * * @note This method sets `removeFromSuperViewOnHide`. The HUDs will automatically be removed from the view hierarchy when hidden. * * @param view The view that is going to be searched for HUD subviews. * @param animated If set to YES the HUDs will disappear using the current animationType. If set to NO the HUDs will not use * animations while disappearing. * @return the number of HUDs found and removed. * * @see hideHUDForView:animated: * @see animationType */ + (NSUInteger)hideAllHUDsForView:(UIView *)view animated:(BOOL)animated; /** * Finds the top-most HUD subview and returns it. * * @param view The view that is going to be searched. * @return A reference to the last HUD subview discovered. */ + (MB_INSTANCETYPE)HUDForView:(UIView *)view; /** * Finds all HUD subviews and returns them. * * @param view The view that is going to be searched. * @return All found HUD views (array of MBProgressHUD objects). */ + (NSArray *)allHUDsForView:(UIView *)view; /** * A convenience constructor that initializes the HUD with the window's bounds. Calls the designated constructor with * window.bounds as the parameter. * * @param window The window instance that will provide the bounds for the HUD. Should be the same instance as * the HUD's superview (i.e., the window that the HUD will be added to). */ - (id)initWithWindow:(UIWindow *)window; /** * A convenience constructor that initializes the HUD with the view's bounds. Calls the designated constructor with * view.bounds as the parameter * * @param view The view instance that will provide the bounds for the HUD. Should be the same instance as * the HUD's superview (i.e., the view that the HUD will be added to). */ - (id)initWithView:(UIView *)view; /** * Display the HUD. You need to make sure that the main thread completes its run loop soon after this method call so * the user interface can be updated. Call this method when your task is already set-up to be executed in a new thread * (e.g., when using something like NSOperation or calling an asynchronous call like NSURLRequest). * * @param animated If set to YES the HUD will appear using the current animationType. If set to NO the HUD will not use * animations while appearing. * * @see animationType */ - (void)show:(BOOL)animated; /** * Hide the HUD. This still calls the hudWasHidden: delegate. This is the counterpart of the show: method. Use it to * hide the HUD when your task completes. * * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use * animations while disappearing. * * @see animationType */ - (void)hide:(BOOL)animated; /** * Hide the HUD after a delay. This still calls the hudWasHidden: delegate. This is the counterpart of the show: method. Use it to * hide the HUD when your task completes. * * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use * animations while disappearing. * @param delay Delay in seconds until the HUD is hidden. * * @see animationType */ - (void)hide:(BOOL)animated afterDelay:(NSTimeInterval)delay; /** * Shows the HUD while a background task is executing in a new thread, then hides the HUD. * * This method also takes care of autorelease pools so your method does not have to be concerned with setting up a * pool. * * @param method The method to be executed while the HUD is shown. This method will be executed in a new thread. * @param target The object that the target method belongs to. * @param object An optional object to be passed to the method. * @param animated If set to YES the HUD will (dis)appear using the current animationType. If set to NO the HUD will not use * animations while (dis)appearing. */ - (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated; #if NS_BLOCKS_AVAILABLE /** * Shows the HUD while a block is executing on a background queue, then hides the HUD. * * @see showAnimated:whileExecutingBlock:onQueue:completionBlock: */ - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block; /** * Shows the HUD while a block is executing on a background queue, then hides the HUD. * * @see showAnimated:whileExecutingBlock:onQueue:completionBlock: */ - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block completionBlock:(MBProgressHUDCompletionBlock)completion; /** * Shows the HUD while a block is executing on the specified dispatch queue, then hides the HUD. * * @see showAnimated:whileExecutingBlock:onQueue:completionBlock: */ - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue; /** * Shows the HUD while a block is executing on the specified dispatch queue, executes completion block on the main queue, and then hides the HUD. * * @param animated If set to YES the HUD will (dis)appear using the current animationType. If set to NO the HUD will * not use animations while (dis)appearing. * @param block The block to be executed while the HUD is shown. * @param queue The dispatch queue on which the block should be executed. * @param completion The block to be executed on completion. * * @see completionBlock */ - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue completionBlock:(MBProgressHUDCompletionBlock)completion; /** * A block that gets called after the HUD was completely hidden. */ @property (copy) MBProgressHUDCompletionBlock completionBlock; #endif /** * MBProgressHUD operation mode. The default is MBProgressHUDModeIndeterminate. * * @see MBProgressHUDMode */ @property (assign) MBProgressHUDMode mode; /** * The animation type that should be used when the HUD is shown and hidden. * * @see MBProgressHUDAnimation */ @property (assign) MBProgressHUDAnimation animationType; /** * The UIView (e.g., a UIImageView) to be shown when the HUD is in MBProgressHUDModeCustomView. * For best results use a 37 by 37 pixel view (so the bounds match the built in indicator bounds). */ @property (MB_STRONG) UIView *customView; /** * The HUD delegate object. * * @see MBProgressHUDDelegate */ @property (MB_WEAK) id delegate; /** * An optional short message to be displayed below the activity indicator. The HUD is automatically resized to fit * the entire text. If the text is too long it will get clipped by displaying "..." at the end. If left unchanged or * set to @"", then no message is displayed. */ @property (copy) NSString *labelText; /** * An optional details message displayed below the labelText message. This message is displayed only if the labelText * property is also set and is different from an empty string (@""). The details text can span multiple lines. */ @property (copy) NSString *detailsLabelText; /** * The opacity of the HUD window. Defaults to 0.8 (80% opacity). */ @property (assign) float opacity; /** * The color of the HUD window. Defaults to black. If this property is set, color is set using * this UIColor and the opacity property is not used. using retain because performing copy on * UIColor base colors (like [UIColor greenColor]) cause problems with the copyZone. */ @property (MB_STRONG) UIColor *color; /** * The x-axis offset of the HUD relative to the centre of the superview. */ @property (assign) float xOffset; /** * The y-axis offset of the HUD relative to the centre of the superview. */ @property (assign) float yOffset; /** * The amount of space between the HUD edge and the HUD elements (labels, indicators or custom views). * Defaults to 20.0 */ @property (assign) float margin; /** * The corner radius for the HUD * Defaults to 10.0 */ @property (assign) float cornerRadius; /** * Cover the HUD background view with a radial gradient. */ @property (assign) BOOL dimBackground; /* * Grace period is the time (in seconds) that the invoked method may be run without * showing the HUD. If the task finishes before the grace time runs out, the HUD will * not be shown at all. * This may be used to prevent HUD display for very short tasks. * Defaults to 0 (no grace time). * Grace time functionality is only supported when the task status is known! * @see taskInProgress */ @property (assign) float graceTime; /** * The minimum time (in seconds) that the HUD is shown. * This avoids the problem of the HUD being shown and than instantly hidden. * Defaults to 0 (no minimum show time). */ @property (assign) float minShowTime; /** * Indicates that the executed operation is in progress. Needed for correct graceTime operation. * If you don't set a graceTime (different than 0.0) this does nothing. * This property is automatically set when using showWhileExecuting:onTarget:withObject:animated:. * When threading is done outside of the HUD (i.e., when the show: and hide: methods are used directly), * you need to set this property when your task starts and completes in order to have normal graceTime * functionality. */ @property (assign) BOOL taskInProgress; /** * Removes the HUD from its parent view when hidden. * Defaults to NO. */ @property (assign) BOOL removeFromSuperViewOnHide; /** * Font to be used for the main label. Set this property if the default is not adequate. */ @property (MB_STRONG) UIFont* labelFont; /** * Color to be used for the main label. Set this property if the default is not adequate. */ @property (MB_STRONG) UIColor* labelColor; /** * Font to be used for the details label. Set this property if the default is not adequate. */ @property (MB_STRONG) UIFont* detailsLabelFont; /** * Color to be used for the details label. Set this property if the default is not adequate. */ @property (MB_STRONG) UIColor* detailsLabelColor; /** * The color of the activity indicator. Defaults to [UIColor whiteColor] * Does nothing on pre iOS 5. */ @property (MB_STRONG) UIColor *activityIndicatorColor; /** * The progress of the progress indicator, from 0.0 to 1.0. Defaults to 0.0. */ @property (assign) float progress; /** * The minimum size of the HUD bezel. Defaults to CGSizeZero (no minimum size). */ @property (assign) CGSize minSize; /** * The actual size of the HUD bezel. * You can use this to limit touch handling on the bezel area only. * @see https://github.com/jdg/MBProgressHUD/pull/200 */ @property (atomic, assign, readonly) CGSize size; /** * Force the HUD dimensions to be equal if possible. */ @property (assign, getter = isSquare) BOOL square; @end @protocol MBProgressHUDDelegate @optional /** * Called after the HUD was fully hidden from the screen. */ - (void)hudWasHidden:(MBProgressHUD *)hud; @end /** * A progress view for showing definite progress by filling up a circle (pie chart). */ @interface MBRoundProgressView : UIView /** * Progress (0.0 to 1.0) */ @property (nonatomic, assign) float progress; /** * Indicator progress color. * Defaults to white [UIColor whiteColor] */ @property (nonatomic, MB_STRONG) UIColor *progressTintColor; /** * Indicator background (non-progress) color. * Defaults to translucent white (alpha 0.1) */ @property (nonatomic, MB_STRONG) UIColor *backgroundTintColor; /* * Display mode - NO = round or YES = annular. Defaults to round. */ @property (nonatomic, assign, getter = isAnnular) BOOL annular; @end /** * A flat bar progress view. */ @interface MBBarProgressView : UIView /** * Progress (0.0 to 1.0) */ @property (nonatomic, assign) float progress; /** * Bar border line color. * Defaults to white [UIColor whiteColor]. */ @property (nonatomic, MB_STRONG) UIColor *lineColor; /** * Bar background color. * Defaults to clear [UIColor clearColor]; */ @property (nonatomic, MB_STRONG) UIColor *progressRemainingColor; /** * Bar progress color. * Defaults to white [UIColor whiteColor]. */ @property (nonatomic, MB_STRONG) UIColor *progressColor; @end ================================================ FILE: Example/Pods/MBProgressHUD/MBProgressHUD.m ================================================ // // MBProgressHUD.m // Version 0.9.2 // Created by Matej Bukovinski on 2.4.09. // #import "MBProgressHUD.h" #import #if __has_feature(objc_arc) #define MB_AUTORELEASE(exp) exp #define MB_RELEASE(exp) exp #define MB_RETAIN(exp) exp #else #define MB_AUTORELEASE(exp) [exp autorelease] #define MB_RELEASE(exp) [exp release] #define MB_RETAIN(exp) [exp retain] #endif #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 #define MBLabelAlignmentCenter NSTextAlignmentCenter #else #define MBLabelAlignmentCenter UITextAlignmentCenter #endif #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 #define MB_TEXTSIZE(text, font) [text length] > 0 ? [text \ sizeWithAttributes:@{NSFontAttributeName:font}] : CGSizeZero; #else #define MB_TEXTSIZE(text, font) [text length] > 0 ? [text sizeWithFont:font] : CGSizeZero; #endif #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 #define MB_MULTILINE_TEXTSIZE(text, font, maxSize, mode) [text length] > 0 ? [text \ boundingRectWithSize:maxSize options:(NSStringDrawingUsesLineFragmentOrigin) \ attributes:@{NSFontAttributeName:font} context:nil].size : CGSizeZero; #else #define MB_MULTILINE_TEXTSIZE(text, font, maxSize, mode) [text length] > 0 ? [text \ sizeWithFont:font constrainedToSize:maxSize lineBreakMode:mode] : CGSizeZero; #endif #ifndef kCFCoreFoundationVersionNumber_iOS_7_0 #define kCFCoreFoundationVersionNumber_iOS_7_0 847.20 #endif #ifndef kCFCoreFoundationVersionNumber_iOS_8_0 #define kCFCoreFoundationVersionNumber_iOS_8_0 1129.15 #endif static const CGFloat kPadding = 4.f; static const CGFloat kLabelFontSize = 16.f; static const CGFloat kDetailsLabelFontSize = 12.f; @interface MBProgressHUD () { BOOL useAnimation; SEL methodForExecution; id targetForExecution; id objectForExecution; UILabel *label; UILabel *detailsLabel; BOOL isFinished; CGAffineTransform rotationTransform; } @property (atomic, MB_STRONG) UIView *indicator; @property (atomic, MB_STRONG) NSTimer *graceTimer; @property (atomic, MB_STRONG) NSTimer *minShowTimer; @property (atomic, MB_STRONG) NSDate *showStarted; @end @implementation MBProgressHUD #pragma mark - Properties @synthesize animationType; @synthesize delegate; @synthesize opacity; @synthesize color; @synthesize labelFont; @synthesize labelColor; @synthesize detailsLabelFont; @synthesize detailsLabelColor; @synthesize indicator; @synthesize xOffset; @synthesize yOffset; @synthesize minSize; @synthesize square; @synthesize margin; @synthesize dimBackground; @synthesize graceTime; @synthesize minShowTime; @synthesize graceTimer; @synthesize minShowTimer; @synthesize taskInProgress; @synthesize removeFromSuperViewOnHide; @synthesize customView; @synthesize showStarted; @synthesize mode; @synthesize labelText; @synthesize detailsLabelText; @synthesize progress; @synthesize size; @synthesize activityIndicatorColor; #if NS_BLOCKS_AVAILABLE @synthesize completionBlock; #endif #pragma mark - Class methods + (MB_INSTANCETYPE)showHUDAddedTo:(UIView *)view animated:(BOOL)animated { MBProgressHUD *hud = [[self alloc] initWithView:view]; hud.removeFromSuperViewOnHide = YES; [view addSubview:hud]; [hud show:animated]; return MB_AUTORELEASE(hud); } + (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated { MBProgressHUD *hud = [self HUDForView:view]; if (hud != nil) { hud.removeFromSuperViewOnHide = YES; [hud hide:animated]; return YES; } return NO; } + (NSUInteger)hideAllHUDsForView:(UIView *)view animated:(BOOL)animated { NSArray *huds = [MBProgressHUD allHUDsForView:view]; for (MBProgressHUD *hud in huds) { hud.removeFromSuperViewOnHide = YES; [hud hide:animated]; } return [huds count]; } + (MB_INSTANCETYPE)HUDForView:(UIView *)view { NSEnumerator *subviewsEnum = [view.subviews reverseObjectEnumerator]; for (UIView *subview in subviewsEnum) { if ([subview isKindOfClass:self]) { return (MBProgressHUD *)subview; } } return nil; } + (NSArray *)allHUDsForView:(UIView *)view { NSMutableArray *huds = [NSMutableArray array]; NSArray *subviews = view.subviews; for (UIView *aView in subviews) { if ([aView isKindOfClass:self]) { [huds addObject:aView]; } } return [NSArray arrayWithArray:huds]; } #pragma mark - Lifecycle - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { // Set default values for properties self.animationType = MBProgressHUDAnimationFade; self.mode = MBProgressHUDModeIndeterminate; self.labelText = nil; self.detailsLabelText = nil; self.opacity = 0.8f; self.color = nil; self.labelFont = [UIFont boldSystemFontOfSize:kLabelFontSize]; self.labelColor = [UIColor whiteColor]; self.detailsLabelFont = [UIFont boldSystemFontOfSize:kDetailsLabelFontSize]; self.detailsLabelColor = [UIColor whiteColor]; self.activityIndicatorColor = [UIColor whiteColor]; self.xOffset = 0.0f; self.yOffset = 0.0f; self.dimBackground = NO; self.margin = 20.0f; self.cornerRadius = 10.0f; self.graceTime = 0.0f; self.minShowTime = 0.0f; self.removeFromSuperViewOnHide = NO; self.minSize = CGSizeZero; self.square = NO; self.contentMode = UIViewContentModeCenter; self.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin; // Transparent background self.opaque = NO; self.backgroundColor = [UIColor clearColor]; // Make it invisible for now self.alpha = 0.0f; taskInProgress = NO; rotationTransform = CGAffineTransformIdentity; [self setupLabels]; [self updateIndicators]; [self registerForKVO]; [self registerForNotifications]; } return self; } - (id)initWithView:(UIView *)view { NSAssert(view, @"View must not be nil."); return [self initWithFrame:view.bounds]; } - (id)initWithWindow:(UIWindow *)window { return [self initWithView:window]; } - (void)dealloc { [self unregisterFromNotifications]; [self unregisterFromKVO]; #if !__has_feature(objc_arc) [color release]; [indicator release]; [label release]; [detailsLabel release]; [labelText release]; [detailsLabelText release]; [graceTimer release]; [minShowTimer release]; [showStarted release]; [customView release]; [labelFont release]; [labelColor release]; [detailsLabelFont release]; [detailsLabelColor release]; #if NS_BLOCKS_AVAILABLE [completionBlock release]; #endif [super dealloc]; #endif } #pragma mark - Show & hide - (void)show:(BOOL)animated { NSAssert([NSThread isMainThread], @"MBProgressHUD needs to be accessed on the main thread."); useAnimation = animated; // If the grace time is set postpone the HUD display if (self.graceTime > 0.0) { NSTimer *newGraceTimer = [NSTimer timerWithTimeInterval:self.graceTime target:self selector:@selector(handleGraceTimer:) userInfo:nil repeats:NO]; [[NSRunLoop currentRunLoop] addTimer:newGraceTimer forMode:NSRunLoopCommonModes]; self.graceTimer = newGraceTimer; } // ... otherwise show the HUD imediately else { [self showUsingAnimation:useAnimation]; } } - (void)hide:(BOOL)animated { NSAssert([NSThread isMainThread], @"MBProgressHUD needs to be accessed on the main thread."); useAnimation = animated; // If the minShow time is set, calculate how long the hud was shown, // and pospone the hiding operation if necessary if (self.minShowTime > 0.0 && showStarted) { NSTimeInterval interv = [[NSDate date] timeIntervalSinceDate:showStarted]; if (interv < self.minShowTime) { self.minShowTimer = [NSTimer scheduledTimerWithTimeInterval:(self.minShowTime - interv) target:self selector:@selector(handleMinShowTimer:) userInfo:nil repeats:NO]; return; } } // ... otherwise hide the HUD immediately [self hideUsingAnimation:useAnimation]; } - (void)hide:(BOOL)animated afterDelay:(NSTimeInterval)delay { [self performSelector:@selector(hideDelayed:) withObject:[NSNumber numberWithBool:animated] afterDelay:delay]; } - (void)hideDelayed:(NSNumber *)animated { [self hide:[animated boolValue]]; } #pragma mark - Timer callbacks - (void)handleGraceTimer:(NSTimer *)theTimer { // Show the HUD only if the task is still running if (taskInProgress) { [self showUsingAnimation:useAnimation]; } } - (void)handleMinShowTimer:(NSTimer *)theTimer { [self hideUsingAnimation:useAnimation]; } #pragma mark - View Hierrarchy - (void)didMoveToSuperview { [self updateForCurrentOrientationAnimated:NO]; } #pragma mark - Internal show & hide operations - (void)showUsingAnimation:(BOOL)animated { // Cancel any scheduled hideDelayed: calls [NSObject cancelPreviousPerformRequestsWithTarget:self]; [self setNeedsDisplay]; if (animated && animationType == MBProgressHUDAnimationZoomIn) { self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(0.5f, 0.5f)); } else if (animated && animationType == MBProgressHUDAnimationZoomOut) { self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(1.5f, 1.5f)); } self.showStarted = [NSDate date]; // Fade in if (animated) { [UIView beginAnimations:nil context:NULL]; [UIView setAnimationDuration:0.30]; self.alpha = 1.0f; if (animationType == MBProgressHUDAnimationZoomIn || animationType == MBProgressHUDAnimationZoomOut) { self.transform = rotationTransform; } [UIView commitAnimations]; } else { self.alpha = 1.0f; } } - (void)hideUsingAnimation:(BOOL)animated { // Fade out if (animated && showStarted) { [UIView beginAnimations:nil context:NULL]; [UIView setAnimationDuration:0.30]; [UIView setAnimationDelegate:self]; [UIView setAnimationDidStopSelector:@selector(animationFinished:finished:context:)]; // 0.02 prevents the hud from passing through touches during the animation the hud will get completely hidden // in the done method if (animationType == MBProgressHUDAnimationZoomIn) { self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(1.5f, 1.5f)); } else if (animationType == MBProgressHUDAnimationZoomOut) { self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(0.5f, 0.5f)); } self.alpha = 0.02f; [UIView commitAnimations]; } else { self.alpha = 0.0f; [self done]; } self.showStarted = nil; } - (void)animationFinished:(NSString *)animationID finished:(BOOL)finished context:(void*)context { [self done]; } - (void)done { [NSObject cancelPreviousPerformRequestsWithTarget:self]; isFinished = YES; self.alpha = 0.0f; if (removeFromSuperViewOnHide) { [self removeFromSuperview]; } #if NS_BLOCKS_AVAILABLE if (self.completionBlock) { self.completionBlock(); self.completionBlock = NULL; } #endif if ([delegate respondsToSelector:@selector(hudWasHidden:)]) { [delegate performSelector:@selector(hudWasHidden:) withObject:self]; } } #pragma mark - Threading - (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated { methodForExecution = method; targetForExecution = MB_RETAIN(target); objectForExecution = MB_RETAIN(object); // Launch execution in new thread self.taskInProgress = YES; [NSThread detachNewThreadSelector:@selector(launchExecution) toTarget:self withObject:nil]; // Show HUD view [self show:animated]; } #if NS_BLOCKS_AVAILABLE - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block { dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:NULL]; } - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block completionBlock:(void (^)())completion { dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:completion]; } - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue { [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:NULL]; } - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue completionBlock:(MBProgressHUDCompletionBlock)completion { self.taskInProgress = YES; self.completionBlock = completion; dispatch_async(queue, ^(void) { block(); dispatch_async(dispatch_get_main_queue(), ^(void) { [self cleanUp]; }); }); [self show:animated]; } #endif - (void)launchExecution { @autoreleasepool { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" // Start executing the requested task [targetForExecution performSelector:methodForExecution withObject:objectForExecution]; #pragma clang diagnostic pop // Task completed, update view in main thread (note: view operations should // be done only in the main thread) [self performSelectorOnMainThread:@selector(cleanUp) withObject:nil waitUntilDone:NO]; } } - (void)cleanUp { taskInProgress = NO; #if !__has_feature(objc_arc) [targetForExecution release]; [objectForExecution release]; #else targetForExecution = nil; objectForExecution = nil; #endif [self hide:useAnimation]; } #pragma mark - UI - (void)setupLabels { label = [[UILabel alloc] initWithFrame:self.bounds]; label.adjustsFontSizeToFitWidth = NO; label.textAlignment = MBLabelAlignmentCenter; label.opaque = NO; label.backgroundColor = [UIColor clearColor]; label.textColor = self.labelColor; label.font = self.labelFont; label.text = self.labelText; [self addSubview:label]; detailsLabel = [[UILabel alloc] initWithFrame:self.bounds]; detailsLabel.font = self.detailsLabelFont; detailsLabel.adjustsFontSizeToFitWidth = NO; detailsLabel.textAlignment = MBLabelAlignmentCenter; detailsLabel.opaque = NO; detailsLabel.backgroundColor = [UIColor clearColor]; detailsLabel.textColor = self.detailsLabelColor; detailsLabel.numberOfLines = 0; detailsLabel.font = self.detailsLabelFont; detailsLabel.text = self.detailsLabelText; [self addSubview:detailsLabel]; } - (void)updateIndicators { BOOL isActivityIndicator = [indicator isKindOfClass:[UIActivityIndicatorView class]]; BOOL isRoundIndicator = [indicator isKindOfClass:[MBRoundProgressView class]]; if (mode == MBProgressHUDModeIndeterminate) { if (!isActivityIndicator) { // Update to indeterminate indicator [indicator removeFromSuperview]; self.indicator = MB_AUTORELEASE([[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]); [(UIActivityIndicatorView *)indicator startAnimating]; [self addSubview:indicator]; } #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 50000 [(UIActivityIndicatorView *)indicator setColor:self.activityIndicatorColor]; #endif } else if (mode == MBProgressHUDModeDeterminateHorizontalBar) { // Update to bar determinate indicator [indicator removeFromSuperview]; self.indicator = MB_AUTORELEASE([[MBBarProgressView alloc] init]); [self addSubview:indicator]; } else if (mode == MBProgressHUDModeDeterminate || mode == MBProgressHUDModeAnnularDeterminate) { if (!isRoundIndicator) { // Update to determinante indicator [indicator removeFromSuperview]; self.indicator = MB_AUTORELEASE([[MBRoundProgressView alloc] init]); [self addSubview:indicator]; } if (mode == MBProgressHUDModeAnnularDeterminate) { [(MBRoundProgressView *)indicator setAnnular:YES]; } [(MBRoundProgressView *)indicator setProgressTintColor:self.activityIndicatorColor]; [(MBRoundProgressView *)indicator setBackgroundTintColor:[self.activityIndicatorColor colorWithAlphaComponent:0.1f]]; } else if (mode == MBProgressHUDModeCustomView && customView != indicator) { // Update custom view indicator [indicator removeFromSuperview]; self.indicator = customView; [self addSubview:indicator]; } else if (mode == MBProgressHUDModeText) { [indicator removeFromSuperview]; self.indicator = nil; } } #pragma mark - Layout - (void)layoutSubviews { [super layoutSubviews]; // Entirely cover the parent view UIView *parent = self.superview; if (parent) { self.frame = parent.bounds; } CGRect bounds = self.bounds; // Determine the total width and height needed CGFloat maxWidth = bounds.size.width - 4 * margin; CGSize totalSize = CGSizeZero; CGRect indicatorF = indicator.bounds; indicatorF.size.width = MIN(indicatorF.size.width, maxWidth); totalSize.width = MAX(totalSize.width, indicatorF.size.width); totalSize.height += indicatorF.size.height; CGSize labelSize = MB_TEXTSIZE(label.text, label.font); labelSize.width = MIN(labelSize.width, maxWidth); totalSize.width = MAX(totalSize.width, labelSize.width); totalSize.height += labelSize.height; if (labelSize.height > 0.f && indicatorF.size.height > 0.f) { totalSize.height += kPadding; } CGFloat remainingHeight = bounds.size.height - totalSize.height - kPadding - 4 * margin; CGSize maxSize = CGSizeMake(maxWidth, remainingHeight); CGSize detailsLabelSize = MB_MULTILINE_TEXTSIZE(detailsLabel.text, detailsLabel.font, maxSize, detailsLabel.lineBreakMode); totalSize.width = MAX(totalSize.width, detailsLabelSize.width); totalSize.height += detailsLabelSize.height; if (detailsLabelSize.height > 0.f && (indicatorF.size.height > 0.f || labelSize.height > 0.f)) { totalSize.height += kPadding; } totalSize.width += 2 * margin; totalSize.height += 2 * margin; // Position elements CGFloat yPos = round(((bounds.size.height - totalSize.height) / 2)) + margin + yOffset; CGFloat xPos = xOffset; indicatorF.origin.y = yPos; indicatorF.origin.x = round((bounds.size.width - indicatorF.size.width) / 2) + xPos; indicator.frame = indicatorF; yPos += indicatorF.size.height; if (labelSize.height > 0.f && indicatorF.size.height > 0.f) { yPos += kPadding; } CGRect labelF; labelF.origin.y = yPos; labelF.origin.x = round((bounds.size.width - labelSize.width) / 2) + xPos; labelF.size = labelSize; label.frame = labelF; yPos += labelF.size.height; if (detailsLabelSize.height > 0.f && (indicatorF.size.height > 0.f || labelSize.height > 0.f)) { yPos += kPadding; } CGRect detailsLabelF; detailsLabelF.origin.y = yPos; detailsLabelF.origin.x = round((bounds.size.width - detailsLabelSize.width) / 2) + xPos; detailsLabelF.size = detailsLabelSize; detailsLabel.frame = detailsLabelF; // Enforce minsize and quare rules if (square) { CGFloat max = MAX(totalSize.width, totalSize.height); if (max <= bounds.size.width - 2 * margin) { totalSize.width = max; } if (max <= bounds.size.height - 2 * margin) { totalSize.height = max; } } if (totalSize.width < minSize.width) { totalSize.width = minSize.width; } if (totalSize.height < minSize.height) { totalSize.height = minSize.height; } size = totalSize; } #pragma mark BG Drawing - (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext(); UIGraphicsPushContext(context); if (self.dimBackground) { //Gradient colours size_t gradLocationsNum = 2; CGFloat gradLocations[2] = {0.0f, 1.0f}; CGFloat gradColors[8] = {0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.75f}; CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, gradColors, gradLocations, gradLocationsNum); CGColorSpaceRelease(colorSpace); //Gradient center CGPoint gradCenter= CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); //Gradient radius float gradRadius = MIN(self.bounds.size.width , self.bounds.size.height) ; //Gradient draw CGContextDrawRadialGradient (context, gradient, gradCenter, 0, gradCenter, gradRadius, kCGGradientDrawsAfterEndLocation); CGGradientRelease(gradient); } // Set background rect color if (self.color) { CGContextSetFillColorWithColor(context, self.color.CGColor); } else { CGContextSetGrayFillColor(context, 0.0f, self.opacity); } // Center HUD CGRect allRect = self.bounds; // Draw rounded HUD backgroud rect CGRect boxRect = CGRectMake(round((allRect.size.width - size.width) / 2) + self.xOffset, round((allRect.size.height - size.height) / 2) + self.yOffset, size.width, size.height); float radius = self.cornerRadius; CGContextBeginPath(context); CGContextMoveToPoint(context, CGRectGetMinX(boxRect) + radius, CGRectGetMinY(boxRect)); CGContextAddArc(context, CGRectGetMaxX(boxRect) - radius, CGRectGetMinY(boxRect) + radius, radius, 3 * (float)M_PI / 2, 0, 0); CGContextAddArc(context, CGRectGetMaxX(boxRect) - radius, CGRectGetMaxY(boxRect) - radius, radius, 0, (float)M_PI / 2, 0); CGContextAddArc(context, CGRectGetMinX(boxRect) + radius, CGRectGetMaxY(boxRect) - radius, radius, (float)M_PI / 2, (float)M_PI, 0); CGContextAddArc(context, CGRectGetMinX(boxRect) + radius, CGRectGetMinY(boxRect) + radius, radius, (float)M_PI, 3 * (float)M_PI / 2, 0); CGContextClosePath(context); CGContextFillPath(context); UIGraphicsPopContext(); } #pragma mark - KVO - (void)registerForKVO { for (NSString *keyPath in [self observableKeypaths]) { [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL]; } } - (void)unregisterFromKVO { for (NSString *keyPath in [self observableKeypaths]) { [self removeObserver:self forKeyPath:keyPath]; } } - (NSArray *)observableKeypaths { return [NSArray arrayWithObjects:@"mode", @"customView", @"labelText", @"labelFont", @"labelColor", @"detailsLabelText", @"detailsLabelFont", @"detailsLabelColor", @"progress", @"activityIndicatorColor", nil]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (![NSThread isMainThread]) { [self performSelectorOnMainThread:@selector(updateUIForKeypath:) withObject:keyPath waitUntilDone:NO]; } else { [self updateUIForKeypath:keyPath]; } } - (void)updateUIForKeypath:(NSString *)keyPath { if ([keyPath isEqualToString:@"mode"] || [keyPath isEqualToString:@"customView"] || [keyPath isEqualToString:@"activityIndicatorColor"]) { [self updateIndicators]; } else if ([keyPath isEqualToString:@"labelText"]) { label.text = self.labelText; } else if ([keyPath isEqualToString:@"labelFont"]) { label.font = self.labelFont; } else if ([keyPath isEqualToString:@"labelColor"]) { label.textColor = self.labelColor; } else if ([keyPath isEqualToString:@"detailsLabelText"]) { detailsLabel.text = self.detailsLabelText; } else if ([keyPath isEqualToString:@"detailsLabelFont"]) { detailsLabel.font = self.detailsLabelFont; } else if ([keyPath isEqualToString:@"detailsLabelColor"]) { detailsLabel.textColor = self.detailsLabelColor; } else if ([keyPath isEqualToString:@"progress"]) { if ([indicator respondsToSelector:@selector(setProgress:)]) { [(id)indicator setValue:@(progress) forKey:@"progress"]; } return; } [self setNeedsLayout]; [self setNeedsDisplay]; } #pragma mark - Notifications - (void)registerForNotifications { #if !TARGET_OS_TV NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; [nc addObserver:self selector:@selector(statusBarOrientationDidChange:) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; #endif } - (void)unregisterFromNotifications { #if !TARGET_OS_TV NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; [nc removeObserver:self name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; #endif } #if !TARGET_OS_TV - (void)statusBarOrientationDidChange:(NSNotification *)notification { UIView *superview = self.superview; if (!superview) { return; } else { [self updateForCurrentOrientationAnimated:YES]; } } #endif - (void)updateForCurrentOrientationAnimated:(BOOL)animated { // Stay in sync with the superview in any case if (self.superview) { self.bounds = self.superview.bounds; [self setNeedsDisplay]; } // Not needed on iOS 8+, compile out when the deployment target allows, // to avoid sharedApplication problems on extension targets #if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000 // Only needed pre iOS 7 when added to a window BOOL iOS8OrLater = kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0; if (iOS8OrLater || ![self.superview isKindOfClass:[UIWindow class]]) return; UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; CGFloat radians = 0; if (UIInterfaceOrientationIsLandscape(orientation)) { if (orientation == UIInterfaceOrientationLandscapeLeft) { radians = -(CGFloat)M_PI_2; } else { radians = (CGFloat)M_PI_2; } // Window coordinates differ! self.bounds = CGRectMake(0, 0, self.bounds.size.height, self.bounds.size.width); } else { if (orientation == UIInterfaceOrientationPortraitUpsideDown) { radians = (CGFloat)M_PI; } else { radians = 0; } } rotationTransform = CGAffineTransformMakeRotation(radians); if (animated) { [UIView beginAnimations:nil context:nil]; [UIView setAnimationDuration:0.3]; } [self setTransform:rotationTransform]; if (animated) { [UIView commitAnimations]; } #endif } @end @implementation MBRoundProgressView #pragma mark - Lifecycle - (id)init { return [self initWithFrame:CGRectMake(0.f, 0.f, 37.f, 37.f)]; } - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { self.backgroundColor = [UIColor clearColor]; self.opaque = NO; _progress = 0.f; _annular = NO; _progressTintColor = [[UIColor alloc] initWithWhite:1.f alpha:1.f]; _backgroundTintColor = [[UIColor alloc] initWithWhite:1.f alpha:.1f]; [self registerForKVO]; } return self; } - (void)dealloc { [self unregisterFromKVO]; #if !__has_feature(objc_arc) [_progressTintColor release]; [_backgroundTintColor release]; [super dealloc]; #endif } #pragma mark - Drawing - (void)drawRect:(CGRect)rect { CGRect allRect = self.bounds; CGRect circleRect = CGRectInset(allRect, 2.0f, 2.0f); CGContextRef context = UIGraphicsGetCurrentContext(); if (_annular) { // Draw background BOOL isPreiOS7 = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0; CGFloat lineWidth = isPreiOS7 ? 5.f : 2.f; UIBezierPath *processBackgroundPath = [UIBezierPath bezierPath]; processBackgroundPath.lineWidth = lineWidth; processBackgroundPath.lineCapStyle = kCGLineCapButt; CGPoint center = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); CGFloat radius = (self.bounds.size.width - lineWidth)/2; CGFloat startAngle = - ((float)M_PI / 2); // 90 degrees CGFloat endAngle = (2 * (float)M_PI) + startAngle; [processBackgroundPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES]; [_backgroundTintColor set]; [processBackgroundPath stroke]; // Draw progress UIBezierPath *processPath = [UIBezierPath bezierPath]; processPath.lineCapStyle = isPreiOS7 ? kCGLineCapRound : kCGLineCapSquare; processPath.lineWidth = lineWidth; endAngle = (self.progress * 2 * (float)M_PI) + startAngle; [processPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES]; [_progressTintColor set]; [processPath stroke]; } else { // Draw background [_progressTintColor setStroke]; [_backgroundTintColor setFill]; CGContextSetLineWidth(context, 2.0f); CGContextFillEllipseInRect(context, circleRect); CGContextStrokeEllipseInRect(context, circleRect); // Draw progress CGPoint center = CGPointMake(allRect.size.width / 2, allRect.size.height / 2); CGFloat radius = (allRect.size.width - 4) / 2; CGFloat startAngle = - ((float)M_PI / 2); // 90 degrees CGFloat endAngle = (self.progress * 2 * (float)M_PI) + startAngle; [_progressTintColor setFill]; CGContextMoveToPoint(context, center.x, center.y); CGContextAddArc(context, center.x, center.y, radius, startAngle, endAngle, 0); CGContextClosePath(context); CGContextFillPath(context); } } #pragma mark - KVO - (void)registerForKVO { for (NSString *keyPath in [self observableKeypaths]) { [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL]; } } - (void)unregisterFromKVO { for (NSString *keyPath in [self observableKeypaths]) { [self removeObserver:self forKeyPath:keyPath]; } } - (NSArray *)observableKeypaths { return [NSArray arrayWithObjects:@"progressTintColor", @"backgroundTintColor", @"progress", @"annular", nil]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { [self setNeedsDisplay]; } @end @implementation MBBarProgressView #pragma mark - Lifecycle - (id)init { return [self initWithFrame:CGRectMake(.0f, .0f, 120.0f, 20.0f)]; } - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { _progress = 0.f; _lineColor = [UIColor whiteColor]; _progressColor = [UIColor whiteColor]; _progressRemainingColor = [UIColor clearColor]; self.backgroundColor = [UIColor clearColor]; self.opaque = NO; [self registerForKVO]; } return self; } - (void)dealloc { [self unregisterFromKVO]; #if !__has_feature(objc_arc) [_lineColor release]; [_progressColor release]; [_progressRemainingColor release]; [super dealloc]; #endif } #pragma mark - Drawing - (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetLineWidth(context, 2); CGContextSetStrokeColorWithColor(context,[_lineColor CGColor]); CGContextSetFillColorWithColor(context, [_progressRemainingColor CGColor]); // Draw background float radius = (rect.size.height / 2) - 2; CGContextMoveToPoint(context, 2, rect.size.height/2); CGContextAddArcToPoint(context, 2, 2, radius + 2, 2, radius); CGContextAddLineToPoint(context, rect.size.width - radius - 2, 2); CGContextAddArcToPoint(context, rect.size.width - 2, 2, rect.size.width - 2, rect.size.height / 2, radius); CGContextAddArcToPoint(context, rect.size.width - 2, rect.size.height - 2, rect.size.width - radius - 2, rect.size.height - 2, radius); CGContextAddLineToPoint(context, radius + 2, rect.size.height - 2); CGContextAddArcToPoint(context, 2, rect.size.height - 2, 2, rect.size.height/2, radius); CGContextFillPath(context); // Draw border CGContextMoveToPoint(context, 2, rect.size.height/2); CGContextAddArcToPoint(context, 2, 2, radius + 2, 2, radius); CGContextAddLineToPoint(context, rect.size.width - radius - 2, 2); CGContextAddArcToPoint(context, rect.size.width - 2, 2, rect.size.width - 2, rect.size.height / 2, radius); CGContextAddArcToPoint(context, rect.size.width - 2, rect.size.height - 2, rect.size.width - radius - 2, rect.size.height - 2, radius); CGContextAddLineToPoint(context, radius + 2, rect.size.height - 2); CGContextAddArcToPoint(context, 2, rect.size.height - 2, 2, rect.size.height/2, radius); CGContextStrokePath(context); CGContextSetFillColorWithColor(context, [_progressColor CGColor]); radius = radius - 2; float amount = self.progress * rect.size.width; // Progress in the middle area if (amount >= radius + 4 && amount <= (rect.size.width - radius - 4)) { CGContextMoveToPoint(context, 4, rect.size.height/2); CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius); CGContextAddLineToPoint(context, amount, 4); CGContextAddLineToPoint(context, amount, radius + 4); CGContextMoveToPoint(context, 4, rect.size.height/2); CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius); CGContextAddLineToPoint(context, amount, rect.size.height - 4); CGContextAddLineToPoint(context, amount, radius + 4); CGContextFillPath(context); } // Progress in the right arc else if (amount > radius + 4) { float x = amount - (rect.size.width - radius - 4); CGContextMoveToPoint(context, 4, rect.size.height/2); CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius); CGContextAddLineToPoint(context, rect.size.width - radius - 4, 4); float angle = -acos(x/radius); if (isnan(angle)) angle = 0; CGContextAddArc(context, rect.size.width - radius - 4, rect.size.height/2, radius, M_PI, angle, 0); CGContextAddLineToPoint(context, amount, rect.size.height/2); CGContextMoveToPoint(context, 4, rect.size.height/2); CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius); CGContextAddLineToPoint(context, rect.size.width - radius - 4, rect.size.height - 4); angle = acos(x/radius); if (isnan(angle)) angle = 0; CGContextAddArc(context, rect.size.width - radius - 4, rect.size.height/2, radius, -M_PI, angle, 1); CGContextAddLineToPoint(context, amount, rect.size.height/2); CGContextFillPath(context); } // Progress is in the left arc else if (amount < radius + 4 && amount > 0) { CGContextMoveToPoint(context, 4, rect.size.height/2); CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius); CGContextAddLineToPoint(context, radius + 4, rect.size.height/2); CGContextMoveToPoint(context, 4, rect.size.height/2); CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius); CGContextAddLineToPoint(context, radius + 4, rect.size.height/2); CGContextFillPath(context); } } #pragma mark - KVO - (void)registerForKVO { for (NSString *keyPath in [self observableKeypaths]) { [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL]; } } - (void)unregisterFromKVO { for (NSString *keyPath in [self observableKeypaths]) { [self removeObserver:self forKeyPath:keyPath]; } } - (NSArray *)observableKeypaths { return [NSArray arrayWithObjects:@"lineColor", @"progressRemainingColor", @"progressColor", @"progress", nil]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { [self setNeedsDisplay]; } @end ================================================ FILE: Example/Pods/MBProgressHUD/README.mdown ================================================ # MBProgressHUD [![Build Status](https://travis-ci.org/matej/MBProgressHUD.svg?branch=master)](https://travis-ci.org/matej/MBProgressHUD) MBProgressHUD is an iOS drop-in class that displays a translucent HUD with an indicator and/or labels while work is being done in a background thread. The HUD is meant as a replacement for the undocumented, private UIKit UIProgressHUD with some additional features. [![](http://dl.dropbox.com/u/378729/MBProgressHUD/1-thumb.png)](http://dl.dropbox.com/u/378729/MBProgressHUD/1.png) [![](http://dl.dropbox.com/u/378729/MBProgressHUD/2-thumb.png)](http://dl.dropbox.com/u/378729/MBProgressHUD/2.png) [![](http://dl.dropbox.com/u/378729/MBProgressHUD/3-thumb.png)](http://dl.dropbox.com/u/378729/MBProgressHUD/3.png) [![](http://dl.dropbox.com/u/378729/MBProgressHUD/4-thumb.png)](http://dl.dropbox.com/u/378729/MBProgressHUD/4.png) [![](http://dl.dropbox.com/u/378729/MBProgressHUD/5-thumb.png)](http://dl.dropbox.com/u/378729/MBProgressHUD/5.png) [![](http://dl.dropbox.com/u/378729/MBProgressHUD/6-thumb.png)](http://dl.dropbox.com/u/378729/MBProgressHUD/6.png) [![](http://dl.dropbox.com/u/378729/MBProgressHUD/7-thumb.png)](http://dl.dropbox.com/u/378729/MBProgressHUD/7.png) ## Requirements MBProgressHUD works on any iOS version and is compatible with both ARC and non-ARC projects. It depends on the following Apple frameworks, which should already be included with most Xcode templates: * Foundation.framework * UIKit.framework * CoreGraphics.framework You will need the latest developer tools in order to build MBProgressHUD. Old Xcode versions might work, but compatibility will not be explicitly maintained. ## Adding MBProgressHUD to your project ### Cocoapods [CocoaPods](http://cocoapods.org) is the recommended way to add MBProgressHUD to your project. 1. Add a pod entry for MBProgressHUD to your Podfile `pod 'MBProgressHUD', '~> 0.9.2'` 2. Install the pod(s) by running `pod install`. 3. Include MBProgressHUD wherever you need it with `#import "MBProgressHUD.h"`. ### Source files Alternatively you can directly add the `MBProgressHUD.h` and `MBProgressHUD.m` source files to your project. 1. Download the [latest code version](https://github.com/matej/MBProgressHUD/archive/master.zip) or add the repository as a git submodule to your git-tracked project. 2. Open your project in Xcode, then drag and drop `MBProgressHUD.h` and `MBProgressHUD.m` onto your project (use the "Product Navigator view"). Make sure to select Copy items when asked if you extracted the code archive outside of your project. 3. Include MBProgressHUD wherever you need it with `#import "MBProgressHUD.h"`. ### Static library You can also add MBProgressHUD as a static library to your project or workspace. 1. Download the [latest code version](https://github.com/matej/MBProgressHUD/downloads) or add the repository as a git submodule to your git-tracked project. 2. Open your project in Xcode, then drag and drop `MBProgressHUD.xcodeproj` onto your project or workspace (use the "Product Navigator view"). 3. Select your target and go to the Build phases tab. In the Link Binary With Libraries section select the add button. On the sheet find and add `libMBProgressHUD.a`. You might also need to add `MBProgressHUD` to the Target Dependencies list. 4. Include MBProgressHUD wherever you need it with `#import `. ## Usage The main guideline you need to follow when dealing with MBProgressHUD while running long-running tasks is keeping the main thread work-free, so the UI can be updated promptly. The recommended way of using MBProgressHUD is therefore to set it up on the main thread and then spinning the task, that you want to perform, off onto a new thread. ```objective-c [MBProgressHUD showHUDAddedTo:self.view animated:YES]; dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ // Do something... dispatch_async(dispatch_get_main_queue(), ^{ [MBProgressHUD hideHUDForView:self.view animated:YES]; }); }); ``` If you need to configure the HUD you can do this by using the MBProgressHUD reference that showHUDAddedTo:animated: returns. ```objective-c MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES]; hud.mode = MBProgressHUDModeAnnularDeterminate; hud.labelText = @"Loading"; [self doSomethingInBackgroundWithProgressCallback:^(float progress) { hud.progress = progress; } completionCallback:^{ [hud hide:YES]; }]; ``` UI updates should always be done on the main thread. Some MBProgressHUD setters are however considered "thread safe" and can be called from background threads. Those also include `setMode:`, `setCustomView:`, `setLabelText:`, `setLabelFont:`, `setDetailsLabelText:`, `setDetailsLabelFont:` and `setProgress:`. If you need to run your long-running task in the main thread, you should perform it with a slight delay, so UIKit will have enough time to update the UI (i.e., draw the HUD) before you block the main thread with your task. ```objective-c [MBProgressHUD showHUDAddedTo:self.view animated:YES]; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 0.01 * NSEC_PER_SEC); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ // Do something... [MBProgressHUD hideHUDForView:self.view animated:YES]; }); ``` You should be aware that any HUD updates issued inside the above block won't be displayed until the block completes. For more examples, including how to use MBProgressHUD with asynchronous operations such as NSURLConnection, take a look at the bundled demo project. Extensive API documentation is provided in the header file (MBProgressHUD.h). ## License This code is distributed under the terms and conditions of the [MIT license](LICENSE). ## Change-log A brief summary of each MBProgressHUD release can be found on the [wiki](https://github.com/matej/MBProgressHUD/wiki/Change-log). ================================================ FILE: Example/Pods/Pods.xcodeproj/project.pbxproj ================================================ archiveVersion 1 classes objectVersion 46 objects 0043FDF25F85C0B2C11CEAE6B1C54D87 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name FBSnapshotTestController.m path FBSnapshotTestCase/FBSnapshotTestController.m sourceTree <group> 00518063767430EDC47FCC49F4409458 fileRef 6CD3EC2C1FBF5655137A99638E9CC950 isa PBXBuildFile 00641C72D7132E5890F1F7098337C3B5 fileRef DF11024BF511A0D2225D4BA84770CED7 isa PBXBuildFile 01F58E863C79FB5A8433D92837FFE619 isa PBXTargetDependency name MBProgressHUD target FE2F1D7B9D9FCEA148517E4657B243F4 targetProxy D47B0B4C04222F6784D38356F02BEB32 021C50274FF43A6F07E119D572C3ACB6 fileRef DA7F16221783A03C084D9BAAF7F404DB isa PBXBuildFile settings ATTRIBUTES Public 02C7E3EC1E1DFDD7046BD26A67E92686 fileRef 981F3EB0C06E97770D2421E673B013A1 isa PBXBuildFile settings ATTRIBUTES Public 033AB851ACA0C118D009F781287565DD fileRef 1E360C94C47826C4CAEEDBFC04C36A42 isa PBXBuildFile settings ATTRIBUTES Public 0364DCB58A865A2830638D2C0CCED41C fileRef DF11024BF511A0D2225D4BA84770CED7 isa PBXBuildFile 043124F225814F80E4ED4FF4BC40156A children 68F141757E1606330C04FEBE78CBCE4E 9F19BCDE9BF6E330E14588598DFC6337 C6FE69AE06A770FF76C19B8039EC0780 BD56223A67203315C1B15E39E3B40934 9C95EBE1D39EEFE5E6404691C95BE2E2 6F1FCA0BD48258CDEC1B14D704BA3E5D 1580BE8D870658FFFB9EF3D04B9213CC isa PBXGroup name Pods-MWPhotoBrowser_Tests path Target Support Files/Pods-MWPhotoBrowser_Tests sourceTree <group> 0498B98EB393D71314953683060EFD58 fileRef EC0223776620A3EA741C855D0211CB0A isa PBXBuildFile settings ATTRIBUTES Public 04D4EDE1F05B96EE8BB9DCD368608620 baseConfigurationReference 805AE44C01D7936BA8A238C6C5C4807D buildSettings ENABLE_STRICT_OBJC_MSGSEND YES GCC_PREFIX_HEADER Target Support Files/Specta/Specta-prefix.pch IPHONEOS_DEPLOYMENT_TARGET 7.0 MTL_ENABLE_DEBUG_INFO NO OTHER_LDFLAGS OTHER_LIBTOOLFLAGS PRIVATE_HEADERS_FOLDER_PATH PRODUCT_NAME $(TARGET_NAME) PUBLIC_HEADERS_FOLDER_PATH SDKROOT iphoneos SKIP_INSTALL YES isa XCBuildConfiguration name Release 0515A6974CBAF0F2F6F72BF87921704B buildConfigurations F6045A7604731086F9E1A9412E290302 589E7F4856ABE929D7FC9F3A9E506FC9 defaultConfigurationIsVisible 0 defaultConfigurationName Release isa XCConfigurationList 051AA4E51A019933DD1E5CE99CD7352A fileRef 0B92B054A2338462A743EB368F25A20B isa PBXBuildFile 05257CC76400D71826E603561A0F73E6 buildConfigurationList 0515A6974CBAF0F2F6F72BF87921704B buildPhases 26D004126E0DEF14D67843A444753FFF 065410F935EC72489896726380B72CD0 9AF3FD4A822DCF644AB50D3967C43C14 buildRules dependencies F601833D09980E16136321674463BE81 0DB9AA4B919BA54CFBC8FC6FD7AFB3F6 isa PBXNativeTarget name Expecta+Snapshots productName Expecta+Snapshots productReference 6CD36438CB08D280978CEE50363D8B8B productType com.apple.product-type.library.static 052A17875CB827423D627183396CEB60 buildSettings ALWAYS_SEARCH_USER_PATHS NO CLANG_CXX_LANGUAGE_STANDARD gnu++0x CLANG_CXX_LIBRARY libc++ CLANG_ENABLE_MODULES YES CLANG_ENABLE_OBJC_ARC YES CLANG_WARN_BOOL_CONVERSION YES CLANG_WARN_CONSTANT_CONVERSION YES CLANG_WARN_DIRECT_OBJC_ISA_USAGE YES CLANG_WARN_EMPTY_BODY YES CLANG_WARN_ENUM_CONVERSION YES CLANG_WARN_INT_CONVERSION YES CLANG_WARN_OBJC_ROOT_CLASS YES CLANG_WARN_UNREACHABLE_CODE YES CLANG_WARN__DUPLICATE_METHOD_MATCH YES COPY_PHASE_STRIP YES ENABLE_NS_ASSERTIONS NO GCC_C_LANGUAGE_STANDARD gnu99 GCC_PREPROCESSOR_DEFINITIONS RELEASE=1 GCC_WARN_64_TO_32_BIT_CONVERSION YES GCC_WARN_ABOUT_RETURN_TYPE YES GCC_WARN_UNDECLARED_SELECTOR YES GCC_WARN_UNINITIALIZED_AUTOS YES GCC_WARN_UNUSED_FUNCTION YES GCC_WARN_UNUSED_VARIABLE YES IPHONEOS_DEPLOYMENT_TARGET 7.0 STRIP_INSTALLED_PRODUCT NO SYMROOT ${SRCROOT}/../build VALIDATE_PRODUCT YES isa XCBuildConfiguration name Release 0557FD60A45F62CD51B4972ECE5C2584 includeInIndex 1 isa PBXFileReference lastKnownFileType text.xcconfig path MBProgressHUD.xcconfig sourceTree <group> 05DA7841521B75C4D0C303F32E518117 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPMatchers+respondTo.h path Expecta/Matchers/EXPMatchers+respondTo.h sourceTree <group> 060435B6E32CF028837E209B20E10639 fileRef C6FE69AE06A770FF76C19B8039EC0780 isa PBXBuildFile 065410F935EC72489896726380B72CD0 buildActionMask 2147483647 files 866569443B7D10AB5D6DC7FC0830235B 9933B0F6DA01EDF804C19260E9515E65 isa PBXFrameworksBuildPhase runOnlyForDeploymentPostprocessing 0 0668957FEB1B3CAE2EABBC4AA9ABA7C3 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name SPTCallSite.m path Specta/Specta/SPTCallSite.m sourceTree <group> 0683DF2C9B7FC27B576AB4B7B3B146ED includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPMatchers+beGreaterThan.h path Expecta/Matchers/EXPMatchers+beGreaterThan.h sourceTree <group> 07282695806D1DFBF187BFA004D80641 fileRef 700F6F4AFED22AEAA1D8943236F94924 isa PBXBuildFile settings ATTRIBUTES Public 072C184628072EDE522629576F1716DE fileRef 5E350F03F362C03BE32737DDE72D4632 isa PBXBuildFile 0732716566238BF76BBF762F85C8ABAF buildConfigurations 617F18A93D7ABF1322E3C027265893C2 85A6795A5CD0C6DEA1F648AD0E6D1248 defaultConfigurationIsVisible 0 defaultConfigurationName Release isa XCConfigurationList 079AA0CF857B4A46F161666C58867CE1 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc path MWPhotoBrowser-dummy.m sourceTree <group> 081F2104425CDCB0915354E2FBD7E24E fileRef 1D536F869C3B7D603AC4630F3F6AB919 isa PBXBuildFile settings ATTRIBUTES Public 098439AB598DDF1AF51146A2E3E2E562 buildActionMask 2147483647 files FC214077677D4F02E03C6C85F774E028 82FC12F67E83874B93592218E9FB4CA0 isa PBXSourcesBuildPhase runOnlyForDeploymentPostprocessing 0 0A924BC847BA99EBCBD0588603448B05 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPBlockDefinedMatcher.h path Expecta/EXPBlockDefinedMatcher.h sourceTree <group> 0A9B8E3E35177F52A643739AC50B00FA includeInIndex 1 isa PBXFileReference path UIBarButtonItemArrowLeft.png sourceTree <group> 0AB1B49E98BEDDFC8A45BB3C1FB54076 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name EXPMatchers+beSubclassOf.m path Expecta/Matchers/EXPMatchers+beSubclassOf.m sourceTree <group> 0B3C605995BE46623C50E7658A1CBE96 fileRef CA8230DE5C83368629EF3E62C4F0E7B7 isa PBXBuildFile settings COMPILER_FLAGS -DOS_OBJECT_USE_OBJC=0 0B7E61991D03B44046913BF3B345F4DB isa PBXFileReference lastKnownFileType wrapper.framework name XCTest.framework path Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.0.sdk/System/Library/Frameworks/XCTest.framework sourceTree DEVELOPER_DIR 0B8D4914244FCBC9DCAF2B9AD09F3DD5 isa PBXTargetDependency name DACircularProgress target 9775596B3CA16905FC8F66DA8E2A3C67 targetProxy 96AE2267633A27498E5D45052BDD5BBA 0B92B054A2338462A743EB368F25A20B includeInIndex 1 isa PBXFileReference path PlayButtonOverlayLargeTap@2x.png sourceTree <group> 0C1A29BFF65C6F2801B1BE0058718A19 fileRef 37E4F69B3E6AFDEE8D68A3430CEC8BF5 isa PBXBuildFile settings ATTRIBUTES Public 0DB9AA4B919BA54CFBC8FC6FD7AFB3F6 isa PBXTargetDependency name FBSnapshotTestCase target 7D9E32DAA93E15CFCF77A5BCD2D4BAA2 targetProxy EE04EE8ADAB8F7DF7FCEFDDD1E801BE6 0DC4183837552F23710F86F9A032C574 fileRef 5D39CEB65ADB521B117CA181C2BB2C9C isa PBXBuildFile settings ATTRIBUTES Public 0EAAE557DE1A2949F0793F1230D1D046 isa PBXTargetDependency name Expecta target 2F501FE84845EAD97B9087DAFCBBEE0E targetProxy A6FD2A9ACF0632CB41BE9AF1A13BD166 0EF1CB072B5DE5D0A4EE7053EB0E2364 fileRef 574FFB37A8EF309B1A1EB265267DD353 isa PBXBuildFile settings ATTRIBUTES Public 0F43FEACB0C18DD4C724B1655AF47728 fileRef 60ECF290B248258F453088B6258302E6 isa PBXBuildFile settings ATTRIBUTES Public 104AFE24D01F1C4495926B40B53C5945 fileRef 92F064516748B6AA4C6E303A38C3FE6A isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc 105B37778FEED1965C8D0AF4C9E4B4F1 fileRef 487E0AD53E31FE34EC41B45FC09B9097 isa PBXBuildFile 10E446637EC5FF03088009E61B491E75 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name SDWebImageCompat.m path SDWebImage/SDWebImageCompat.m sourceTree <group> 10E68FD4A81F628778B6A4992DB9D952 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPMatchers+beTruthy.h path Expecta/Matchers/EXPMatchers+beTruthy.h sourceTree <group> 1181CB6FC2D63C42F19EAB56C604A71E fileRef A09DD0AB6A512D74E5628070EBD56026 isa PBXBuildFile 1186EC1A35306EBBA8C3DE66F0973361 fileRef B0DFED38A647950C79B13061F923258C isa PBXBuildFile 11CF0C44A36897A963C15B74C2AEC415 fileRef 3DEC242716623D70F4CC9F4A1D32805C isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc 123355D7F0D268DB0998FFB1CDD859B9 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h path MBProgressHUD-prefix.pch sourceTree <group> 1246A1C00980422020A6884856143651 buildActionMask 2147483647 files 0C1A29BFF65C6F2801B1BE0058718A19 0F43FEACB0C18DD4C724B1655AF47728 isa PBXHeadersBuildPhase runOnlyForDeploymentPostprocessing 0 1398D1B84667A17FCB66182F776EAC42 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name SPTSharedExampleGroups.h path Specta/Specta/SPTSharedExampleGroups.h sourceTree <group> 13EA87F4C750EBB98261F8ED14EFE770 isa PBXTargetDependency name Specta target E9AAC3590B391F4CFAF5F8348E6A8FE4 targetProxy 659BCA5950BB094273E9E83E7D5271CF 143C0831AA95D723669324010D835391 buildActionMask 2147483647 files CF95446EA555B49150EA7270096D78B2 487899F028C39C1A518547A1AB2F625A FDF72740DBC37AFACFED73ED42282383 11CF0C44A36897A963C15B74C2AEC415 4383E0DB1B07B9EB3155EF5D5F27C7BA 104AFE24D01F1C4495926B40B53C5945 779CFE8771E1EF63F1313ABEBCECAA4A 49EFE75BAF060A33327F3CE8C18436F2 5F6D96E64F890BDC4A75B73C3D50A0DD F1F4E65611F5567A86AF797EAC3E225B A7141BC83638F4B38D4D312BAE3BDAC4 CB08C9C83ABDBE55762A423ED48491EF AD6791D14732A3C17164F61CC72FFB0D A65C491577A425AF82C53F4A40A0A24B 2F9D3747596E4E074C3B776949091047 7436C602BB1CA7C91560C28DE749357B 63D0CD4F0FB5A6103E1DDB46E876CBB6 C6671739E8C5904113586F8BEBBC9780 43A1104CA0C628C2F693902EADA32B8C E06376455C1D5E45B97ACDC5438FC15B BD30B724A71CF5D6E93805B7615EC79C 6D37BEAA1FC469C3582CACB4E9766502 5E8F33E777456DA63CA2D902508A9058 C2BFF99EB859FD7056CF72C4850693D1 46F312CBB94BAE62B58D3D7AE28E8DBD B33234F432A72D5E8B65694AE937B78F 8059E5674B08670B0A002D564FFABF44 562BE99A6F630E709218EB9B3CF36E90 2239B5E63C5D2C1323D66F489F075C42 69EBB956302554EA37775F4D806BC4DD 7FEE0E8D094D7BCCAC7129473EE05ADC A26F992E8831118311F3DB7CB830595A E2EBD18BA89D3FF648947DF31FA12D44 40F505E69B8595361C2A7528DDA222B6 872948DAF79618AD725E0BF364E5DDD4 isa PBXSourcesBuildPhase runOnlyForDeploymentPostprocessing 0 148F0DEDC306B5A6D48931EFD279E456 children EF5AC1222120F3BAB7481DA9615352B0 4594A9E3FFD6ABE129882E959BB04B5E D5DEC16E7CEEFEABBFEBAC3A10358C5A isa PBXGroup name Support Files path ../Target Support Files/FBSnapshotTestCase sourceTree <group> 14BD3072FD1820402768B3D7F2E5B4A5 fileRef 8952D588C0E5AD12FD9CF3747FD061F2 isa PBXBuildFile settings ATTRIBUTES Public 14C609D8F203FD45194E93997EFF744E fileRef 71865C216F48AC822D486C8D9B03B13C isa PBXBuildFile settings ATTRIBUTES Public 15082EF0D1C2F7B77B63E0116BDA622C includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name EXPFloatTuple.m path Expecta/EXPFloatTuple.m sourceTree <group> 1580BE8D870658FFFB9EF3D04B9213CC includeInIndex 1 isa PBXFileReference lastKnownFileType text.xcconfig path Pods-MWPhotoBrowser_Tests.release.xcconfig sourceTree <group> 1629A676793A787618A75ABDA6E20C56 fileRef 8C4EC29461A90C294949DAE717A1BEC5 isa PBXBuildFile 164BD2AFD03035E623A90DF1AA095C35 fileRef 3B5A7151F8E4FBFE54BF6C2AC80958FA isa PBXBuildFile settings ATTRIBUTES Public 1684C2FDDE674F14B731CC940021D8C6 buildActionMask 2147483647 files 00641C72D7132E5890F1F7098337C3B5 8C4ECAE46CD3BCB39F38F4FF20D074F6 isa PBXFrameworksBuildPhase runOnlyForDeploymentPostprocessing 0 16A268BA14BC07320BB7FBE44EB37FB4 fileRef 3CD608194F35F47CEC250368B43C48E9 isa PBXBuildFile settings ATTRIBUTES Public 18391089432560093C31CCA48ABB46AE includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name NSValue+Expecta.h path Expecta/NSValue+Expecta.h sourceTree <group> 189B7D1A1C865073D7F999A759ACD292 buildConfigurations FD6FA76757975A1FC954269CE5739798 257A97CE59C7E5D5191DC00F15C261BE defaultConfigurationIsVisible 0 defaultConfigurationName Release isa XCConfigurationList 199EF76AC47B3EE9FA80BE53835AB7B1 buildActionMask 2147483647 files 66081D3480D1FA028C4DE2344BF616D4 69363A59A4E2FF95D6A62AA88641A20A isa PBXFrameworksBuildPhase runOnlyForDeploymentPostprocessing 0 1A0D2B172232171C3EB6DC4DFE575EE7 fileRef 079AA0CF857B4A46F161666C58867CE1 isa PBXBuildFile 1A1E6F8D4C651A22BB8CCEF1226FB1EE includeInIndex 1 isa PBXFileReference path PlayButtonOverlayLarge@2x.png sourceTree <group> 1A1E8E8494B9BFCEBBD8273148D44386 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name UIImage+MultiFormat.h path SDWebImage/UIImage+MultiFormat.h sourceTree <group> 1AB26E75F7BCD415D6AD6B8BA5F03DDB includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name EXPMatchers+beIdenticalTo.m path Expecta/Matchers/EXPMatchers+beIdenticalTo.m sourceTree <group> 1AFDF0F0EF2A314336EACA38FCC6F784 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name SPTExample.m path Specta/Specta/SPTExample.m sourceTree <group> 1B030B50B68FB0D602F2E4B1685FA9BA isa PBXTargetDependency name MWPhotoBrowser target E2FE87384A67337831E53490EEA8BF11 targetProxy B6CE0371E70FB931CC99A90CEBFD6E83 1B0ADBF8A991262A6DE5BD0CB101EEE0 children 0557FD60A45F62CD51B4972ECE5C2584 FC1172043A5F0542D166D63AE9EBC260 123355D7F0D268DB0998FFB1CDD859B9 isa PBXGroup name Support Files path ../Target Support Files/MBProgressHUD sourceTree <group> 1BB15402D8C2FB01A198273BA1991DED includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name EXPMatchers+beginWith.m path Expecta/Matchers/EXPMatchers+beginWith.m sourceTree <group> 1C1837DF96AE65F662773DF1956BFF09 baseConfigurationReference 805AE44C01D7936BA8A238C6C5C4807D buildSettings ENABLE_STRICT_OBJC_MSGSEND YES GCC_PREFIX_HEADER Target Support Files/Specta/Specta-prefix.pch IPHONEOS_DEPLOYMENT_TARGET 7.0 MTL_ENABLE_DEBUG_INFO YES OTHER_LDFLAGS OTHER_LIBTOOLFLAGS PRIVATE_HEADERS_FOLDER_PATH PRODUCT_NAME $(TARGET_NAME) PUBLIC_HEADERS_FOLDER_PATH SDKROOT iphoneos SKIP_INSTALL YES isa XCBuildConfiguration name Debug 1CCE02DFD89EF9B58809C379D0496685 isa PBXTargetDependency name Expecta+Snapshots target 05257CC76400D71826E603561A0F73E6 targetProxy FE4A445C1692178764D9C71EAC64ABAC 1D536F869C3B7D603AC4630F3F6AB919 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name ExpectaObject.h path Expecta/ExpectaObject.h sourceTree <group> 1D772DC880BCFABF5028DA167DE6B119 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name SPTExcludeGlobalBeforeAfterEach.h path Specta/Specta/SPTExcludeGlobalBeforeAfterEach.h sourceTree <group> 1DD364708B768EB7C94DD9BB48F217F4 fileRef D54993E311EDD8802711B01895286E4E isa PBXBuildFile settings ATTRIBUTES Public 1E08912939425D011D7C47713E6CDAF0 fileRef BD4E094BE48FCAB50C0CEFC92B2C0B04 isa PBXBuildFile 1E360C94C47826C4CAEEDBFC04C36A42 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name XCTestCase+Specta.h path Specta/Specta/XCTestCase+Specta.h sourceTree <group> 1EF011C5AA1726984A1B50A46191CE9B includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name XCTest+Private.h path Specta/Specta/XCTest+Private.h sourceTree <group> 20503FB88D4DCF32DC61DD92AA97A82B fileRef 6C8B1153C440825B90681905FC2F85B9 isa PBXBuildFile settings ATTRIBUTES Public 2055A9E17F10DAA3DD7884DDF96A0A50 includeInIndex 1 isa PBXFileReference lastKnownFileType text.xcconfig path MWPhotoBrowser.xcconfig sourceTree <group> 216490FDC8376C36DB920710C65B92F3 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc path MWTapDetectingImageView.m sourceTree <group> 216D98400141172993D43965B937A9C6 containerPortal D41D8CD98F00B204E9800998ECF8427E isa PBXContainerItemProxy proxyType 1 remoteGlobalIDString DE3B07ED7FF04F506B0C5E00F4973589 remoteInfo SDWebImage 222A85D6DAB7D3AC98EF6D7E68B13740 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name SDWebImageManager.h path SDWebImage/SDWebImageManager.h sourceTree <group> 2239B5E63C5D2C1323D66F489F075C42 fileRef 7250BEF99A7686EAB50B17986FDCED3E isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc 232510EC13760470E86AABE302EF2B8E includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name EXPMatcherHelpers.m path Expecta/Matchers/EXPMatcherHelpers.m sourceTree <group> 237FB063FB365119546C7B5006224F3B fileRef AB8960E6933BD274C11AD9C15CF410AF isa PBXBuildFile settings ATTRIBUTES Public 244F3301C3C77C58A95FA7864D67858D includeInIndex 1 isa PBXFileReference path ImageSelectedSmallOff@2x.png sourceTree <group> 24FAEADA1249F6042B4B4B135CDD0DF9 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name EXPMatchers+respondTo.m path Expecta/Matchers/EXPMatchers+respondTo.m sourceTree <group> 25003C5ED979935F9483683DD2350697 children FDEE1AFEA9C68977819B7D652EE1F6F2 2ECFFDC2A7A648A930A461436E2ECC2D 6C92311B12AB812820A4267248D69CCB isa PBXGroup name MWPhotoBrowser path ../.. sourceTree <group> 257A97CE59C7E5D5191DC00F15C261BE baseConfigurationReference EF5AC1222120F3BAB7481DA9615352B0 buildSettings ENABLE_STRICT_OBJC_MSGSEND YES GCC_PREFIX_HEADER Target Support Files/FBSnapshotTestCase/FBSnapshotTestCase-prefix.pch IPHONEOS_DEPLOYMENT_TARGET 7.0 MTL_ENABLE_DEBUG_INFO NO OTHER_LDFLAGS OTHER_LIBTOOLFLAGS PRIVATE_HEADERS_FOLDER_PATH PRODUCT_NAME $(TARGET_NAME) PUBLIC_HEADERS_FOLDER_PATH SDKROOT iphoneos SKIP_INSTALL YES isa XCBuildConfiguration name Release 2677C6B3B248613A05ABDFB423C9F02C children EB4DE2009B511D62CF6BFE67FA73E321 9374BBB3BA16684D1B858CCC9C3E2D8A 523087C6D186A9F5BEE78D39F9A78F3F 71BC183B1FB7AB5F8CF9A3383C6B354C C9B97F514121309E4531C3439E206EC7 D5EE5F7C5A846B01ACF225DE04E52196 B83D4C05E77991780CA69BF68B431DD2 isa PBXGroup name Pods-MWPhotoBrowser_Example path Target Support Files/Pods-MWPhotoBrowser_Example sourceTree <group> 26D004126E0DEF14D67843A444753FFF buildActionMask 2147483647 files 8B97E5CA574F06B315C5F1D27B25DEFB 1629A676793A787618A75ABDA6E20C56 isa PBXSourcesBuildPhase runOnlyForDeploymentPostprocessing 0 27499458B06BE275AED9ACF720F09853 fileRef 1D772DC880BCFABF5028DA167DE6B119 isa PBXBuildFile settings ATTRIBUTES Public 2779B434631901F1434BC316E3FB5AE9 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPMatchers+beNil.h path Expecta/Matchers/EXPMatchers+beNil.h sourceTree <group> 28A20AE3D7A1F50CAD0D9D8B71205EC6 includeInIndex 1 isa PBXFileReference path ImageSelectedOn@2x.png sourceTree <group> 29062DA699B861B896758C5A677765BD children 2677C6B3B248613A05ABDFB423C9F02C 043124F225814F80E4ED4FF4BC40156A isa PBXGroup name Targets Support Files sourceTree <group> 2945FAA75C956DD6A541EB51E42E6899 fileRef FDE2A8151D1FDB6749FB32276220BE7C isa PBXBuildFile settings ATTRIBUTES Public 2989492A1A182744E36FD61DF1746D94 fileRef DF11024BF511A0D2225D4BA84770CED7 isa PBXBuildFile 299B50B04F45E1A2F52075C3B7A1AEB2 isa PBXTargetDependency name SDWebImage target DE3B07ED7FF04F506B0C5E00F4973589 targetProxy 90C5595E0565CBEBB42EFEBB157FF619 29B6CC3E1560257A444A209D97AC271E fileRef 7599165E41821009340AA99FDAC29AE0 isa PBXBuildFile settings COMPILER_FLAGS -DOS_OBJECT_USE_OBJC=0 29E4E0528D088AE8B2EE6D38E19D375E fileRef 6C982719FDCF6684EB752A0153066FC3 isa PBXBuildFile settings COMPILER_FLAGS -DOS_OBJECT_USE_OBJC=0 29E6F040DB8ACF8886F7FE628840C480 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPMatchers+beKindOf.h path Expecta/Matchers/EXPMatchers+beKindOf.h sourceTree <group> 2A17721E4A81DB608CA6D4FB6F0ADAFB fileRef 3E7FFEF7BFB0350CE8FCC2D6D64D1396 isa PBXBuildFile settings ATTRIBUTES Public 2ACE82A15131DC569A786CB49EFD1804 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name SPTCallSite.h path Specta/Specta/SPTCallSite.h sourceTree <group> 2BFB267A4274D5D4B277138F9D4EEDA1 fileRef 2EDD6193448F7939784FDF1C8016F000 isa PBXBuildFile settings COMPILER_FLAGS -DOS_OBJECT_USE_OBJC=0 2D5EE41C6DDBA8F159EEA954F335387A isa PBXTargetDependency name MBProgressHUD target FE2F1D7B9D9FCEA148517E4657B243F4 targetProxy FF6817E7D2D11A6D747BFAEA934D0FD1 2D6EC33CA8D9BD953AD29E16068F2CBD includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name SDImageCache.h path SDWebImage/SDImageCache.h sourceTree <group> 2D8E8EC45A3A1A1D94AE762CB5028504 buildConfigurations B37F0F91F85060E28F1DAAB522DC7EC1 052A17875CB827423D627183396CEB60 defaultConfigurationIsVisible 0 defaultConfigurationName Release isa XCConfigurationList 2DE374580729793C277006A3481402A3 includeInIndex 1 isa PBXFileReference path VideoOverlay@2x.png sourceTree <group> 2E7CBF297AA8A0E93C23BFAF1BE94DEE includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPMatchers+beIdenticalTo.h path Expecta/Matchers/EXPMatchers+beIdenticalTo.h sourceTree <group> 2ECFFDC2A7A648A930A461436E2ECC2D children 37ACF2CCD5DA793FAF5F71CF8F469294 isa PBXGroup name Resources sourceTree <group> 2EDD6193448F7939784FDF1C8016F000 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name SDWebImageManager.m path SDWebImage/SDWebImageManager.m sourceTree <group> 2F29BE2EDEBA2861368726A4772F4E8E includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name SPTExample.h path Specta/Specta/SPTExample.h sourceTree <group> 2F501FE84845EAD97B9087DAFCBBEE0E buildConfigurationList 57205495CF13B4EE93B13B7B0E3A1BD2 buildPhases 143C0831AA95D723669324010D835391 199EF76AC47B3EE9FA80BE53835AB7B1 C6AD7852D34E8A80DFF3B8376BDE812F buildRules dependencies isa PBXNativeTarget name Expecta productName Expecta productReference ADEAC9FE1270035973745D0EB3B84395 productType com.apple.product-type.library.static 2F9D3747596E4E074C3B776949091047 fileRef 44D0D04C0DF98EC05B4909F397D6F7FF isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc 2FD7E39AFBAB884FA64CFB5DBDFA1F7B children CBE0BF23E2B3F39D22B51BC93022DE7D B73F44C64FD91479CFE63C882F1E5E70 isa PBXGroup name SDWebImage path SDWebImage sourceTree <group> 311D5C7B166178CCB79CFD7E8DD4F641 fileRef D91F4635A8EDF54921FC9B759BE009F6 isa PBXBuildFile settings ATTRIBUTES Public 3173089CCB6C063D260C58DD7BA3ABF5 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name EXPMatchers+beKindOf.m path Expecta/Matchers/EXPMatchers+beKindOf.m sourceTree <group> 319D6926D1261EB1150D83F672CCA35E fileRef A09DD0AB6A512D74E5628070EBD56026 isa PBXBuildFile 32A9515F32A6ACB0753681241864BABA fileRef F1948E226356BF652752F9FA7317BC73 isa PBXBuildFile settings COMPILER_FLAGS -DOS_OBJECT_USE_OBJC=0 33985216BF501EDD56E784D7CAD32FE7 fileRef B1A8AF86363A5D8093FFE48748DC20A1 isa PBXBuildFile 339A0C1BFF72397A705959E03877DDDB fileRef 5F878B29A406784703BC9F07FBFDD99F isa PBXBuildFile settings ATTRIBUTES Public 341D7536159B52F41598F730CC45D548 fileRef 8136A5BFC35DA48ABC2C0D94EA79B7F0 isa PBXBuildFile settings ATTRIBUTES Public 34B2272FAB199A0B69B898BFB9E733E1 fileRef DE369A67A17A1EE2351376730BB039A4 isa PBXBuildFile settings COMPILER_FLAGS -DOS_OBJECT_USE_OBJC=0 34F30E1314EC4C287E6B89477C2D0FB4 fileRef 8E485F27A3C78223FB086075C2974A49 isa PBXBuildFile settings ATTRIBUTES Public 3540CB186FE2EE9EE99381DA600738B8 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h path EXPMatchers+FBSnapshotTest.h sourceTree <group> 362D9038C23D156F9F055EDF5565A5F1 children 7D8198AD59277CB0379442369CF5247E 730E6CDCF09A029F8976D82EB7DACCF2 69C3D12EC1F2F8AE628C05AC390D9D61 C402F1446422CAB85FC53BD77E5E9018 FCA01B035C5FC9AF0969977B2C54A7A9 5A531485C6A0D185C72E03CD91B49919 A8B86384F8C46CE8544B59FEB6C657F5 D8619A771228AD0402C4D464E3C6EE53 C0A33FBAF4E7C28807891EC8D0B65204 9D6702575943BF5C71AD0E2299CB53E2 AABA7597E19FD75C9F320BCD6BB2759E C266061F84E4CFB93AF99D21EEB13401 564C3E7A9D55850E48FAC93F77A7F478 6C8B1153C440825B90681905FC2F85B9 216490FDC8376C36DB920710C65B92F3 8952D588C0E5AD12FD9CF3747FD061F2 94A310E9C2919B9904E9AFBD2837C2B9 AE6C39B87AE391EC710DA8FB520ED3F5 A1195ED094D0C8B66BB073959AA2ACCD E7E794ADE2D5267DF8949DD0A42B935B 5289709799A254B807701A658BD9D425 isa PBXGroup name Classes path Classes sourceTree <group> 37726FF893DE97CB8E4497EED1D4F28E fileRef 0668957FEB1B3CAE2EABBC4AA9ABA7C3 isa PBXBuildFile settings COMPILER_FLAGS -DOS_OBJECT_USE_OBJC=0 37ACF2CCD5DA793FAF5F71CF8F469294 children 4F696B27FDAEF5BE2C52EFF1848239D6 isa PBXGroup name Pod path Pod sourceTree <group> 37E4F69B3E6AFDEE8D68A3430CEC8BF5 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name DACircularProgressView.h path DACircularProgress/DACircularProgressView.h sourceTree <group> 386634C7797B60DF71C8AD13DAAD315D containerPortal D41D8CD98F00B204E9800998ECF8427E isa PBXContainerItemProxy proxyType 1 remoteGlobalIDString 9775596B3CA16905FC8F66DA8E2A3C67 remoteInfo DACircularProgress 38777F32EB3F13ED8D2047C1C9ED6D6D includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name UIView+WebCacheOperation.m path SDWebImage/UIView+WebCacheOperation.m sourceTree <group> 3907B3A4464A3069E24142505C76338D buildActionMask 2147483647 files AAF71AC633AFEC1D271F81DA183E75F0 5116C7E5987E156AC4BAA9BF327459AD 5728F0AC6D99470A4AF64DA015991D65 16A268BA14BC07320BB7FBE44EB37FB4 87A80C8B1F0C635DA56B74807AE426D9 45F8F3DCD4679DF7E346A6589E02123D B2DE78D2D5A5CF173FEBFA2FE4B4023D 7D56DF05F0071D94121A050CDB772D56 27499458B06BE275AED9ACF720F09853 C2420EB9004B4667C6328B5C0D098C48 E40400D97F14785D20A7A5594F0E2C56 904DB6CCA2AAC8D7B3245ED9628E865A 4A0F7A4BC9D18113AABC29DECF4E51D1 FEFA85FDDB15B89CF28DF12C1C2071E1 033AB851ACA0C118D009F781287565DD isa PBXHeadersBuildPhase runOnlyForDeploymentPostprocessing 0 3939EE53B37025D6F0E05FF8A4C7DCF0 fileRef AE1B7431EAFB4D89A49A9DAD35DBE790 isa PBXBuildFile settings ATTRIBUTES Public 3A013F13122CDB8EE962F8CB7C6FCC8E fileRef DFA142DA2A1E6AB454178652BAC9A075 isa PBXBuildFile settings ATTRIBUTES Public 3B332CC633C291A7A96AD0922CFD8AB0 fileRef 2D6EC33CA8D9BD953AD29E16068F2CBD isa PBXBuildFile settings ATTRIBUTES Public 3B5A7151F8E4FBFE54BF6C2AC80958FA includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name UIImage+Diff.h path FBSnapshotTestCase/UIImage+Diff.h sourceTree <group> 3C5A51B8AE7322A210CF01AFBA648598 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPMatchers+beInstanceOf.h path Expecta/Matchers/EXPMatchers+beInstanceOf.h sourceTree <group> 3CAA21F3728659DC1B58A336233AEB79 includeInIndex 1 isa PBXFileReference path PlayButtonOverlayLarge.png sourceTree <group> 3CD608194F35F47CEC250368B43C48E9 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name SpectaUtility.h path Specta/Specta/SpectaUtility.h sourceTree <group> 3DA2CF997A09E53F375CF0DEEF25AFCF includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name EXPMatchers+postNotification.m path Expecta/Matchers/EXPMatchers+postNotification.m sourceTree <group> 3DEC242716623D70F4CC9F4A1D32805C includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name ExpectaObject.m path Expecta/ExpectaObject.m sourceTree <group> 3E463E2B6917D9AA08A03BA8A8E74A18 fileRef B5969E17FAFFDC548233DC602AAC6211 isa PBXBuildFile settings ATTRIBUTES Public 3E7FFEF7BFB0350CE8FCC2D6D64D1396 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPExpect.h path Expecta/EXPExpect.h sourceTree <group> 3EF2F2E1AF840BA0B6AD13D59AA5F80C includeInIndex 1 isa PBXFileReference path PlayButtonOverlayLargeTap@3x.png sourceTree <group> 3F1E0CB31108E6E996D2E7418FE21D2E fileRef 661962DFFB8F4AF681F8EA78582EE534 isa PBXBuildFile settings COMPILER_FLAGS -DOS_OBJECT_USE_OBJC=0 40D39B6435EEF47A85F4E3FB684E72FE includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name DACircularProgressView.m path DACircularProgress/DACircularProgressView.m sourceTree <group> 40DAD22BCDD38D058B1C9A312C42DF49 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name EXPMatchers+beNil.m path Expecta/Matchers/EXPMatchers+beNil.m sourceTree <group> 40F505E69B8595361C2A7528DDA222B6 fileRef B23D9E49B5B3A5C76EAFFC9C7F40B4B9 isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc 4209F7E4332FDC16B4C407CA07C3E8E8 children 86A1ADA0F8A843FA09BFF9142BEF76D5 A6A21F67B2A713138BF1A404E3460084 990C6F27E60B10F0E4BA0674D80F546B isa PBXGroup name Support Files path ../Target Support Files/Expecta sourceTree <group> 4231743B6C143BDB4A5FBB032E6D3799 fileRef 865FAF448207E691D600F40C4ACF7C58 isa PBXBuildFile settings ATTRIBUTES Public 427921605A450AEF67D129DACCD87BED includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc path MBProgressHUD.m sourceTree <group> 430CE433DB59FE090A8CC6AFCFA43337 fileRef D10BA2216A9B5F41557B4FC448EE865C isa PBXBuildFile settings ATTRIBUTES Public 4358F868C1553FF4273B0726E5AD2012 includeInIndex 1 isa PBXFileReference path ImageError.png sourceTree <group> 4383E0DB1B07B9EB3155EF5D5F27C7BA fileRef 4BE4F23029A1EDB234CF7F2AEB043EB0 isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc 439E7363C4854DD4D1E46F32C2EA2F3B fileRef 8F2B443FA0D511403E4DA2A79DB3302A isa PBXBuildFile settings COMPILER_FLAGS -DOS_OBJECT_USE_OBJC=0 43A1104CA0C628C2F693902EADA32B8C fileRef 746A80915CF52BE732D200BAA851FBAB isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc 4463CD014E8C258B7F8BF0FD64901B8F fileRef 63327617AD4F38D1236E71968B0991E5 isa PBXBuildFile 44D0D04C0DF98EC05B4909F397D6F7FF includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name EXPMatchers+beInstanceOf.m path Expecta/Matchers/EXPMatchers+beInstanceOf.m sourceTree <group> 453678659359A72C87495DDD6D9BD0C6 fileRef 730E6CDCF09A029F8976D82EB7DACCF2 isa PBXBuildFile 45548268D1B9EE9D37731231D940A591 fileRef 4594A9E3FFD6ABE129882E959BB04B5E isa PBXBuildFile 4594A9E3FFD6ABE129882E959BB04B5E includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc path FBSnapshotTestCase-dummy.m sourceTree <group> 45F8F3DCD4679DF7E346A6589E02123D fileRef 569060A075537DFE6A4D25D96C98668B isa PBXBuildFile settings ATTRIBUTES Public 46A2CE1545DEBE5FE9DF073C8EBFE2F0 buildActionMask 2147483647 files F5F4D07451FB3E10EE7C16A83010425F B8FC063009C06CE659C8CF168A9F8765 isa PBXFrameworksBuildPhase runOnlyForDeploymentPostprocessing 0 46F312CBB94BAE62B58D3D7AE28E8DBD fileRef 9D5AE460F3B78B02A8AE8E5AD0DCC013 isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc 46F871A74D831312A633CCFC0A94F75D includeInIndex 1 isa PBXFileReference path ImageSelectedSmallOn.png sourceTree <group> 47BC96C02F9C3E09817E3FEC2559E33B includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name SDWebImageDownloaderOperation.h path SDWebImage/SDWebImageDownloaderOperation.h sourceTree <group> 487899F028C39C1A518547A1AB2F625A fileRef D70BED84191514A998A08D7F3B8BBB17 isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc 487E0AD53E31FE34EC41B45FC09B9097 includeInIndex 1 isa PBXFileReference path UIBarButtonItemArrowLeft@3x.png sourceTree <group> 49D43B7D98ABA386628BDA9FB6BA411B includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name SPTGlobalBeforeAfterEach.h path Specta/Specta/SPTGlobalBeforeAfterEach.h sourceTree <group> 49EFE75BAF060A33327F3CE8C18436F2 fileRef 232510EC13760470E86AABE302EF2B8E isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc 4A0F7A4BC9D18113AABC29DECF4E51D1 fileRef EF75C10714B7CFF9071553F07D94A550 isa PBXBuildFile settings ATTRIBUTES Public 4B72A26F758BB4CA3663A7FB67696029 fileRef DBE203B988A948E71FDDCAE62BA1F4CC isa PBXBuildFile settings COMPILER_FLAGS -DOS_OBJECT_USE_OBJC=0 4BE4F23029A1EDB234CF7F2AEB043EB0 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name ExpectaSupport.m path Expecta/ExpectaSupport.m sourceTree <group> 4C9C7890ED44DBC3090EEDFD92749F0A buildActionMask 2147483647 files B733330A3935B54DE57E0503C1867DAE 9C8319C4AFAB3CAE3C7314EAD8D1AB34 4B72A26F758BB4CA3663A7FB67696029 37726FF893DE97CB8E4497EED1D4F28E EB75F515760C184B2ED65E8649DC5792 67BBF4202A90EC070C5EBE20B57A0255 E127589B46F33617D24F341DBAE1E90D 439E7363C4854DD4D1E46F32C2EA2F3B 32A9515F32A6ACB0753681241864BABA D9E225250B8DAA16A4D7B6A66EC6574A 0B3C605995BE46623C50E7658A1CBE96 isa PBXSourcesBuildPhase runOnlyForDeploymentPostprocessing 0 4CFA073D420411E3535ABA966BEE98CC includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name UIImage+GIF.h path SDWebImage/UIImage+GIF.h sourceTree <group> 4CFCAA497A77475D017F781F8241A4C0 fileRef 5E350F03F362C03BE32737DDE72D4632 isa PBXBuildFile 4D720BB8AE19E427E58843509B28FEB6 explicitFileType archive.ar includeInIndex 0 isa PBXFileReference name libPods-MWPhotoBrowser_Example.a path libPods-MWPhotoBrowser_Example.a sourceTree BUILT_PRODUCTS_DIR 4DCDDE359E1F3BB419E0F7E9DD12CBB6 fileRef 6CA9CE30C9B7FD5557F8DD0E06209E36 isa PBXBuildFile 4F696B27FDAEF5BE2C52EFF1848239D6 children 4358F868C1553FF4273B0726E5AD2012 ABDF044BC66387B0E03E70BAC09D0AC8 BA9181C8B2773795F2EACBDE286994AD 6CA9CE30C9B7FD5557F8DD0E06209E36 E6798A1D89E725C8EB02146EE4B0E31E D2307EC60BB4D23E49C1420E6F192943 F2334A461A36CF285AC6F7E96F8FE0DB 28A20AE3D7A1F50CAD0D9D8B71205EC6 C9BF70FB69B8740763B185C23C560434 BD4E094BE48FCAB50C0CEFC92B2C0B04 244F3301C3C77C58A95FA7864D67858D 60E1461AAED23D358B1A25A48F9DD77F 46F871A74D831312A633CCFC0A94F75D E4436D58F27EAC932F3CC2A7619D5FD3 CB3EA5CC56581FD52BDBA0179D956E7B 3CAA21F3728659DC1B58A336233AEB79 1A1E6F8D4C651A22BB8CCEF1226FB1EE 75F83323F205A277BEC18AABDA0FA739 6D7C34F3A270979025E31A50FFFC7F28 0B92B054A2338462A743EB368F25A20B 3EF2F2E1AF840BA0B6AD13D59AA5F80C 0A9B8E3E35177F52A643739AC50B00FA C36BB9623ECA2B27942B66986A784EC9 487E0AD53E31FE34EC41B45FC09B9097 BDD78DD77E210708F222D40A2FDE75B1 8DC04A9EEA14F0D2300FB6C0CAEB9CAB 7323B64F45BBE15A70D98D5E7FBF31C8 C162177585DC8E186D24CADCC233B0EA 8439940A3793CF65B24F4185E04317D8 D64041D7A1004D2C8771C29362B59366 8F3064A5B520D1E4290AA240A05B7D6F 2DE374580729793C277006A3481402A3 9604D510A96A637DD9AE0D304B68A5A2 isa PBXGroup name Assets path Assets sourceTree <group> 5023A97277B66BB3E83DA779FEBAE80C fileRef 94A310E9C2919B9904E9AFBD2837C2B9 isa PBXBuildFile 505D7B9189B81314EF02A1E9479972D0 children 3540CB186FE2EE9EE99381DA600738B8 8C4EC29461A90C294949DAE717A1BEC5 6772D2471C34E5B39F0D3E7D0E235DA0 isa PBXGroup name Expecta+Snapshots path Expecta+Snapshots sourceTree <group> 50F87BFDC26A0B4CC6DE06D3ACBD0714 explicitFileType archive.ar includeInIndex 0 isa PBXFileReference name libMWPhotoBrowser.a path libMWPhotoBrowser.a sourceTree BUILT_PRODUCTS_DIR 5116C7E5987E156AC4BAA9BF327459AD fileRef 9BF2584F8D336EF737485D7396CFBAFF isa PBXBuildFile settings ATTRIBUTES Public 518626B93DA6269F02901D111B4DBF26 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name UIButton+WebCache.h path SDWebImage/UIButton+WebCache.h sourceTree <group> 518937C0924DA3A3DD216697CED9205F fileRef D615D83AA6E624C9EEFAB11B6DAB4612 isa PBXBuildFile settings ATTRIBUTES Public 523087C6D186A9F5BEE78D39F9A78F3F includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc path Pods-MWPhotoBrowser_Example-dummy.m sourceTree <group> 5289709799A254B807701A658BD9D425 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc path UIImage+MWPhotoBrowser.m sourceTree <group> 53A65E3DDAAB4288F448625574686617 fileRef A59CA631962D8BFBF1D8BA4BA8F1F223 isa PBXBuildFile settings COMPILER_FLAGS -DOS_OBJECT_USE_OBJC=0 53FA1A78374A52A66FBFD0691645BCB8 buildActionMask 2147483647 files B25B4A401CE0322E5FA936642D8E8A36 E145A7E23B46A8C1E1549511735A041D DD7BDEE57A8E95F9F327AC87B6A78256 BEEAFC79B66B12E6D67D140381BE25F2 618AF4F15226C5AED0802286F3763EF4 B52658FC373BA42C6DBF475CAEA30F09 FFF549D1FBC0937A99BED40CC44BC155 760A4E328E68E43D12295FA08F7475FD 20503FB88D4DCF32DC61DD92AA97A82B 14BD3072FD1820402768B3D7F2E5B4A5 FB42B2F11B39412F3015A082008F1782 E54B859019C3D44B55F4723880F93D4D isa PBXHeadersBuildPhase runOnlyForDeploymentPostprocessing 0 53FB0CBA14BE53F79F4EB4E7B46C523B fileRef DF11024BF511A0D2225D4BA84770CED7 isa PBXBuildFile 56228B3CBF246784930BFE7B29EFDD04 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name SPTCompiledExample.m path Specta/Specta/SPTCompiledExample.m sourceTree <group> 562BE99A6F630E709218EB9B3CF36E90 fileRef 6CF3D336192327338965DC88856BDFCF isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc 564C3E7A9D55850E48FAC93F77A7F478 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h path MWPhotoProtocol.h sourceTree <group> 567A47FABAD9EE8B7DF32295CC53F937 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPMatchers+endWith.h path Expecta/Matchers/EXPMatchers+endWith.h sourceTree <group> 569060A075537DFE6A4D25D96C98668B includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name SPTCompiledExample.h path Specta/Specta/SPTCompiledExample.h sourceTree <group> 56BD615DEEA3BC153C0B73BAC8A60A13 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name EXPMatchers+endWith.m path Expecta/Matchers/EXPMatchers+endWith.m sourceTree <group> 571096112AA1AD00C10596D216783599 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name SPTExampleGroup.m path Specta/Specta/SPTExampleGroup.m sourceTree <group> 57205495CF13B4EE93B13B7B0E3A1BD2 buildConfigurations F84C57B89A97BB8108482354FFD8E157 9D77DAC945EC22411297CBC957C966F0 defaultConfigurationIsVisible 0 defaultConfigurationName Release isa XCConfigurationList 5728F0AC6D99470A4AF64DA015991D65 fileRef 6347B71E8C22BA00F73AB093F101B7D4 isa PBXBuildFile settings ATTRIBUTES Public 5748A5F5926815AF7CCC45FA80943763 fileRef B0329B325C97BC663835390843F985CE isa PBXBuildFile settings ATTRIBUTES Public 574FFB37A8EF309B1A1EB265267DD353 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name SDWebImageDownloader.h path SDWebImage/SDWebImageDownloader.h sourceTree <group> 5813251FEA21E4AEE97385709F1912DE buildConfigurationList 9F1294BB80138FB7DEE240DF61EC1523 buildPhases DE0797009C8330FFDB1FC0EB8D2DA9DD F2BDF8E898C762EFB3844211CE201024 9987EAA3314FDCBDC6783C325695E1D8 buildRules dependencies isa PBXNativeTarget name MWPhotoBrowser-MWPhotoBrowser productName MWPhotoBrowser-MWPhotoBrowser productReference 8261C05543EA69BA5F136A06D5005C50 productType com.apple.product-type.bundle 589CDFB90C98BF58F34B688A2BEF2D04 fileRef DF11024BF511A0D2225D4BA84770CED7 isa PBXBuildFile 589E7F4856ABE929D7FC9F3A9E506FC9 baseConfigurationReference FAF77DDAA41AC524C38F1D92F2EE01AB buildSettings ENABLE_STRICT_OBJC_MSGSEND YES GCC_PREFIX_HEADER Target Support Files/Expecta+Snapshots/Expecta+Snapshots-prefix.pch IPHONEOS_DEPLOYMENT_TARGET 7.0 MTL_ENABLE_DEBUG_INFO NO OTHER_LDFLAGS OTHER_LIBTOOLFLAGS PRIVATE_HEADERS_FOLDER_PATH PRODUCT_NAME $(TARGET_NAME) PUBLIC_HEADERS_FOLDER_PATH SDKROOT iphoneos SKIP_INSTALL YES isa XCBuildConfiguration name Release 59DA6D130464A9F97CD181877DD36CB7 fileRef 6B1C830074F8BFC3AE3CB3A5278754A8 isa PBXBuildFile settings COMPILER_FLAGS -DOS_OBJECT_USE_OBJC=0 59E65BF5E40AA0C59E0BBC07807714F1 baseConfigurationReference D5EE5F7C5A846B01ACF225DE04E52196 buildSettings ENABLE_STRICT_OBJC_MSGSEND YES IPHONEOS_DEPLOYMENT_TARGET 7.0 MACH_O_TYPE staticlib MTL_ENABLE_DEBUG_INFO YES OTHER_LDFLAGS OTHER_LIBTOOLFLAGS PODS_ROOT $(SRCROOT) PRODUCT_NAME $(TARGET_NAME) SDKROOT iphoneos SKIP_INSTALL YES isa XCBuildConfiguration name Debug 5A06D9F124A077FE3345C713135D7E6C includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name NSValue+Expecta.m path Expecta/NSValue+Expecta.m sourceTree <group> 5A531485C6A0D185C72E03CD91B49919 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h path MWGridViewController.h sourceTree <group> 5A859FF87C3507C937241DD78BD95EF3 fileRef 6D7C34F3A270979025E31A50FFFC7F28 isa PBXBuildFile 5CD9AEB21836919899CDA3A9D9510998 buildConfigurations 59E65BF5E40AA0C59E0BBC07807714F1 5DBFCD132D088BC182C9B82D9A6F90D9 defaultConfigurationIsVisible 0 defaultConfigurationName Release isa XCConfigurationList 5D39CEB65ADB521B117CA181C2BB2C9C includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name UIImageView+HighlightedWebCache.h path SDWebImage/UIImageView+HighlightedWebCache.h sourceTree <group> 5DBFCD132D088BC182C9B82D9A6F90D9 baseConfigurationReference B83D4C05E77991780CA69BF68B431DD2 buildSettings ENABLE_STRICT_OBJC_MSGSEND YES IPHONEOS_DEPLOYMENT_TARGET 7.0 MACH_O_TYPE staticlib MTL_ENABLE_DEBUG_INFO NO OTHER_LDFLAGS OTHER_LIBTOOLFLAGS PODS_ROOT $(SRCROOT) PRODUCT_NAME $(TARGET_NAME) SDKROOT iphoneos SKIP_INSTALL YES isa XCBuildConfiguration name Release 5E350F03F362C03BE32737DDE72D4632 isa PBXFileReference lastKnownFileType wrapper.framework name QuartzCore.framework path Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.0.sdk/System/Library/Frameworks/QuartzCore.framework sourceTree DEVELOPER_DIR 5E8F33E777456DA63CA2D902508A9058 fileRef C2CEB9C1175C4FC4D5BE5C6EF285E0A4 isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc 5EB4F27B76513AE918BA5AE0868E87C8 fileRef EF4D256729962DC82677BF90E861294B isa PBXBuildFile settings ATTRIBUTES Public 5EB6A7FF5E92B3E2E96A92367BE4AA32 containerPortal D41D8CD98F00B204E9800998ECF8427E isa PBXContainerItemProxy proxyType 1 remoteGlobalIDString 7D9E32DAA93E15CFCF77A5BCD2D4BAA2 remoteInfo FBSnapshotTestCase 5F4AECEF356E35995112F9167B219AD6 fileRef AF944A6486FDAD944BE766A70129E6D2 isa PBXBuildFile 5F6D96E64F890BDC4A75B73C3D50A0DD fileRef D68BA9A93FD415E751DFA95CCF7DCB8D isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc 5F878B29A406784703BC9F07FBFDD99F includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPMatchers+postNotification.h path Expecta/Matchers/EXPMatchers+postNotification.h sourceTree <group> 60262C4B68C1B0EF7D3E4FADACB10CD7 fileRef 222A85D6DAB7D3AC98EF6D7E68B13740 isa PBXBuildFile settings ATTRIBUTES Public 60D3CCEB5B53542228790ABD8885AF42 fileRef 18391089432560093C31CCA48ABB46AE isa PBXBuildFile settings ATTRIBUTES Public 60E1461AAED23D358B1A25A48F9DD77F includeInIndex 1 isa PBXFileReference path ImageSelectedSmallOff@3x.png sourceTree <group> 60ECF290B248258F453088B6258302E6 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name DALabeledCircularProgressView.h path DACircularProgress/DALabeledCircularProgressView.h sourceTree <group> 617F18A93D7ABF1322E3C027265893C2 baseConfigurationReference 2055A9E17F10DAA3DD7884DDF96A0A50 buildSettings ENABLE_STRICT_OBJC_MSGSEND YES GCC_PREFIX_HEADER Target Support Files/MWPhotoBrowser/MWPhotoBrowser-prefix.pch IPHONEOS_DEPLOYMENT_TARGET 7.0 MTL_ENABLE_DEBUG_INFO YES OTHER_LDFLAGS OTHER_LIBTOOLFLAGS PRIVATE_HEADERS_FOLDER_PATH PRODUCT_NAME $(TARGET_NAME) PUBLIC_HEADERS_FOLDER_PATH SDKROOT iphoneos SKIP_INSTALL YES isa XCBuildConfiguration name Debug 618AF4F15226C5AED0802286F3763EF4 fileRef D8619A771228AD0402C4D464E3C6EE53 isa PBXBuildFile settings ATTRIBUTES Public 619B0B2F7ACD42F8EF700EAE6D1C95DA buildConfigurations CB7148E744A9D03AEDC9D4809248999F 86334F1A4DF0160C4B098EF643AB6E07 defaultConfigurationIsVisible 0 defaultConfigurationName Release isa XCConfigurationList 63010FB3922AB3E2DDD6D8CDD2D10BFA includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name EXPMatchers+conformTo.m path Expecta/Matchers/EXPMatchers+conformTo.m sourceTree <group> 63327617AD4F38D1236E71968B0991E5 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name FBSnapshotTestCase.m path FBSnapshotTestCase/FBSnapshotTestCase.m sourceTree <group> 6347B71E8C22BA00F73AB093F101B7D4 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name SpectaTypes.h path Specta/Specta/SpectaTypes.h sourceTree <group> 63B3276116DCC036DC3FF64871C5B34A fileRef 9BDA7CB400BA41525D0DAC5E267B2670 isa PBXBuildFile settings COMPILER_FLAGS -DOS_OBJECT_USE_OBJC=0 63D0CD4F0FB5A6103E1DDB46E876CBB6 fileRef 3173089CCB6C063D260C58DD7BA3ABF5 isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc 64734F2BA63DAFDA24F819EE71704665 fileRef 7323B64F45BBE15A70D98D5E7FBF31C8 isa PBXBuildFile 659BCA5950BB094273E9E83E7D5271CF containerPortal D41D8CD98F00B204E9800998ECF8427E isa PBXContainerItemProxy proxyType 1 remoteGlobalIDString E9AAC3590B391F4CFAF5F8348E6A8FE4 remoteInfo Specta 65F75ED4B34CEDF568441FBC95BA1459 fileRef 244F3301C3C77C58A95FA7864D67858D isa PBXBuildFile 66081D3480D1FA028C4DE2344BF616D4 fileRef DF11024BF511A0D2225D4BA84770CED7 isa PBXBuildFile 661962DFFB8F4AF681F8EA78582EE534 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name UIButton+WebCache.m path SDWebImage/UIButton+WebCache.m sourceTree <group> 664FB9826D0F046AB6CE15196CC120B7 fileRef A1BF1E794449AF3F514B2EE3C85F4B10 isa PBXBuildFile 66D391F04A8D672C0D42510DE811E7B2 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name NSObject+Expecta.h path Expecta/NSObject+Expecta.h sourceTree <group> 6772D2471C34E5B39F0D3E7D0E235DA0 children FAF77DDAA41AC524C38F1D92F2EE01AB 785A993DF42B3ABEF658B85E1F47663F F5551459CFCB6B7914E9EC1783277D94 isa PBXGroup name Support Files path ../Target Support Files/Expecta+Snapshots sourceTree <group> 67BBF4202A90EC070C5EBE20B57A0255 fileRef 1AFDF0F0EF2A314336EACA38FCC6F784 isa PBXBuildFile settings COMPILER_FLAGS -DOS_OBJECT_USE_OBJC=0 688EFB46F8559BD675FD06384ABA49E1 fileRef C162177585DC8E186D24CADCC233B0EA isa PBXBuildFile 68F141757E1606330C04FEBE78CBCE4E includeInIndex 1 isa PBXFileReference lastKnownFileType text path Pods-MWPhotoBrowser_Tests-acknowledgements.markdown sourceTree <group> 691CE6B20E64762C98682B93B31435E7 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name SPTSpec.h path Specta/Specta/SPTSpec.h sourceTree <group> 6924E116731D7079958063A3EE0781ED fileRef 6ED6BE9A61002A3AF6D4C834E2277D8C isa PBXBuildFile settings ATTRIBUTES Public 69363A59A4E2FF95D6A62AA88641A20A fileRef 0B7E61991D03B44046913BF3B345F4DB isa PBXBuildFile 69C3D12EC1F2F8AE628C05AC390D9D61 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h path MWCommon.h sourceTree <group> 69DC2FB163EAB23712D9C3C13C72518E fileRef C77F8C54B4042EC0746E5D285E7D400A isa PBXBuildFile settings ATTRIBUTES Public 69EBB956302554EA37775F4D806BC4DD fileRef 3DA2CF997A09E53F375CF0DEEF25AFCF isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc 6A4E3ACA285A21392936C110E7EC91F0 fileRef 7AAD7709C9FF6828905D6A28E69C4221 isa PBXBuildFile settings ATTRIBUTES Public 6B1C830074F8BFC3AE3CB3A5278754A8 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name UIImage+GIF.m path SDWebImage/UIImage+GIF.m sourceTree <group> 6C8B1153C440825B90681905FC2F85B9 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h path MWTapDetectingImageView.h sourceTree <group> 6C92311B12AB812820A4267248D69CCB children 2055A9E17F10DAA3DD7884DDF96A0A50 079AA0CF857B4A46F161666C58867CE1 BA4C6CCE8BCEAFA07BB2679C87A008C5 isa PBXGroup name Support Files path Example/Pods/Target Support Files/MWPhotoBrowser sourceTree <group> 6C982719FDCF6684EB752A0153066FC3 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name SDWebImageDecoder.m path SDWebImage/SDWebImageDecoder.m sourceTree <group> 6CA9CE30C9B7FD5557F8DD0E06209E36 includeInIndex 1 isa PBXFileReference path ImageSelectedOff.png sourceTree <group> 6CD36438CB08D280978CEE50363D8B8B explicitFileType archive.ar includeInIndex 0 isa PBXFileReference name libExpecta+Snapshots.a path libExpecta+Snapshots.a sourceTree BUILT_PRODUCTS_DIR 6CD3EC2C1FBF5655137A99638E9CC950 isa PBXFileReference lastKnownFileType wrapper.framework name MediaPlayer.framework path Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.0.sdk/System/Library/Frameworks/MediaPlayer.framework sourceTree DEVELOPER_DIR 6CF3D336192327338965DC88856BDFCF includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name EXPMatchers+haveCountOf.m path Expecta/Matchers/EXPMatchers+haveCountOf.m sourceTree <group> 6D37BEAA1FC469C3582CACB4E9766502 fileRef EC9E28E1F941952C6708902F41FD209D isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc 6D7C34F3A270979025E31A50FFFC7F28 includeInIndex 1 isa PBXFileReference path PlayButtonOverlayLargeTap.png sourceTree <group> 6D8154E68B3360EE1C8DA3A4443096F3 isa PBXTargetDependency name SDWebImage target DE3B07ED7FF04F506B0C5E00F4973589 targetProxy 216D98400141172993D43965B937A9C6 6E43863A0284BCD9B517C3AE49207A9D buildActionMask 2147483647 files 9D6E638E56BDAD3C2196ED9C28FAF857 1DD364708B768EB7C94DD9BB48F217F4 723DCD855EFE5AC009B1D2FFE88373C7 5748A5F5926815AF7CCC45FA80943763 164BD2AFD03035E623A90DF1AA095C35 isa PBXHeadersBuildPhase runOnlyForDeploymentPostprocessing 0 6EB2498C2AFB1DF8555CB7C1EF89CA5C fileRef F5963246D32249D4D198CE8A36B4DD12 isa PBXBuildFile settings ATTRIBUTES Public 6ED6BE9A61002A3AF6D4C834E2277D8C includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPMatchers.h path Expecta/Matchers/EXPMatchers.h sourceTree <group> 6EEA87D8E87914D02AA319693371EE2C fileRef AABA7597E19FD75C9F320BCD6BB2759E isa PBXBuildFile 6F1FCA0BD48258CDEC1B14D704BA3E5D includeInIndex 1 isa PBXFileReference lastKnownFileType text.xcconfig path Pods-MWPhotoBrowser_Tests.debug.xcconfig sourceTree <group> 6FAC17B355970FC2EBFADA7C72544799 buildActionMask 2147483647 files D041545E1DB9D43FCBB294B7E9573AFF isa PBXFrameworksBuildPhase runOnlyForDeploymentPostprocessing 0 700F6F4AFED22AEAA1D8943236F94924 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPMatcher.h path Expecta/EXPMatcher.h sourceTree <group> 70545E4EA86C6E593A2A9F6731DA8F6D fileRef 10E68FD4A81F628778B6A4992DB9D952 isa PBXBuildFile settings ATTRIBUTES Public 70922EF029715CA28E814F87373F29C4 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name EXPMatchers+equal.m path Expecta/Matchers/EXPMatchers+equal.m sourceTree <group> 70D801E0B95C23BCC270E4EC8659D463 fileRef DB7D9B8599935A5FF3862B90E049CABD isa PBXBuildFile 717B38CC8B651B61F35E12EE4211DAF0 baseConfigurationReference 2055A9E17F10DAA3DD7884DDF96A0A50 buildSettings ENABLE_STRICT_OBJC_MSGSEND YES PRODUCT_NAME MWPhotoBrowser SDKROOT iphoneos SKIP_INSTALL YES WRAPPER_EXTENSION bundle isa XCBuildConfiguration name Debug 71865C216F48AC822D486C8D9B03B13C includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPMatchers+beSubclassOf.h path Expecta/Matchers/EXPMatchers+beSubclassOf.h sourceTree <group> 719353E5F5BDD19B2D80B273F365F2BF children 25003C5ED979935F9483683DD2350697 isa PBXGroup name Development Pods sourceTree <group> 71BC183B1FB7AB5F8CF9A3383C6B354C includeInIndex 1 isa PBXFileReference lastKnownFileType text.script.sh path Pods-MWPhotoBrowser_Example-frameworks.sh sourceTree <group> 722090AAE01BA442E883E6F02AA8DF40 buildConfigurationList 8BEDD694C8600B014302155F48FD5B08 buildPhases A07F4B785B315E5B075218A988F05149 6FAC17B355970FC2EBFADA7C72544799 buildRules dependencies 0B8D4914244FCBC9DCAF2B9AD09F3DD5 0EAAE557DE1A2949F0793F1230D1D046 1CCE02DFD89EF9B58809C379D0496685 9F0508C4938477BDB11EAF1F3A6960E7 01F58E863C79FB5A8433D92837FFE619 A815E988348FB8A8B609F012516ABD15 D359D2CDD896874DAD7B6D2EF3FEE26D 13EA87F4C750EBB98261F8ED14EFE770 isa PBXNativeTarget name Pods-MWPhotoBrowser_Tests productName Pods-MWPhotoBrowser_Tests productReference E0EBF0357895E0C657A233A9D4D0E2C1 productType com.apple.product-type.library.static 723DCD855EFE5AC009B1D2FFE88373C7 fileRef 8E108520195BF6568A4A53786EA7B6F0 isa PBXBuildFile settings ATTRIBUTES Public 7250BEF99A7686EAB50B17986FDCED3E includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name EXPMatchers+match.m path Expecta/Matchers/EXPMatchers+match.m sourceTree <group> 730E6CDCF09A029F8976D82EB7DACCF2 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc path MWCaptionView.m sourceTree <group> 7323B64F45BBE15A70D98D5E7FBF31C8 includeInIndex 1 isa PBXFileReference path UIBarButtonItemArrowRight@3x.png sourceTree <group> 7357CD98F48585C602A75C901AD5E096 buildActionMask 2147483647 files 63B3276116DCC036DC3FF64871C5B34A 34B2272FAB199A0B69B898BFB9E733E1 33985216BF501EDD56E784D7CAD32FE7 EDDE097EFD40E9A4E32E4B44C97CE44B 29E4E0528D088AE8B2EE6D38E19D375E 53A65E3DDAAB4288F448625574686617 29B6CC3E1560257A444A209D97AC271E 2BFB267A4274D5D4B277138F9D4EEDA1 DC1F1DBD53679CB78C5967BA9DEC08CA 3F1E0CB31108E6E996D2E7418FE21D2E 59DA6D130464A9F97CD181877DD36CB7 B28719F6B1978267C407032A2AE646F8 DFAB389ECAD69BF8BE6357CFADABBF5C 7D3B3786F1D6CED2CE141B2B1BDAE012 F3BB0A963277282C6BF73E81250B6943 isa PBXSourcesBuildPhase runOnlyForDeploymentPostprocessing 0 7389D03E90A74E5A200DD5ADBB209885 includeInIndex 1 isa PBXFileReference lastKnownFileType text.xcconfig path DACircularProgress.xcconfig sourceTree <group> 7436C602BB1CA7C91560C28DE749357B fileRef F9B2375A6E20F2774E7B3F08FB92909E isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc 746A80915CF52BE732D200BAA851FBAB includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name EXPMatchers+beLessThanOrEqualTo.m path Expecta/Matchers/EXPMatchers+beLessThanOrEqualTo.m sourceTree <group> 74707D5ABEC55B3084F52C40A4227B06 fileRef 2779B434631901F1434BC316E3FB5AE9 isa PBXBuildFile settings ATTRIBUTES Public 74A9CCFEBE692130454C41959EBA5254 fileRef 8A00437F88A0C89CD672C2085CCCF62B isa PBXBuildFile settings ATTRIBUTES Public 7599165E41821009340AA99FDAC29AE0 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name SDWebImageDownloaderOperation.m path SDWebImage/SDWebImageDownloaderOperation.m sourceTree <group> 75F83323F205A277BEC18AABDA0FA739 includeInIndex 1 isa PBXFileReference path PlayButtonOverlayLarge@3x.png sourceTree <group> 760A4E328E68E43D12295FA08F7475FD fileRef 564C3E7A9D55850E48FAC93F77A7F478 isa PBXBuildFile settings ATTRIBUTES Public 76331E71086C8CD5118A69B046D8F0FB fileRef 567A47FABAD9EE8B7DF32295CC53F937 isa PBXBuildFile settings ATTRIBUTES Public 76C42C41A4B61435A3315770F58154B2 fileRef 8F3064A5B520D1E4290AA240A05B7D6F isa PBXBuildFile 779CFE8771E1EF63F1313ABEBCECAA4A fileRef 15082EF0D1C2F7B77B63E0116BDA622C isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc 785A993DF42B3ABEF658B85E1F47663F includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc path Expecta+Snapshots-dummy.m sourceTree <group> 78759DF877B83416A90AA2BC606BF7ED includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name SDWebImagePrefetcher.m path SDWebImage/SDWebImagePrefetcher.m sourceTree <group> 788D548DE837CA2FA9D2D744D64FA847 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name EXPMatchers+raise.m path Expecta/Matchers/EXPMatchers+raise.m sourceTree <group> 79996E262256F57DB50CD16E5EBE5D52 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPMatcherHelpers.h path Expecta/Matchers/EXPMatcherHelpers.h sourceTree <group> 79D73FB4B6A3C92513B81243EA72E78C includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPMatchers+match.h path Expecta/Matchers/EXPMatchers+match.h sourceTree <group> 7AAD7709C9FF6828905D6A28E69C4221 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPDefines.h path Expecta/EXPDefines.h sourceTree <group> 7AC91F55DAAA2F0223A97BEFF8BCAF68 fileRef D09A95F4452843967F4C8F3FA5EC1A68 isa PBXBuildFile settings ATTRIBUTES Public 7CF892960748F43245AD2B5CD2AC41A5 explicitFileType archive.ar includeInIndex 0 isa PBXFileReference name libSpecta.a path libSpecta.a sourceTree BUILT_PRODUCTS_DIR 7D3B3786F1D6CED2CE141B2B1BDAE012 fileRef A2B692D3F450E25A5D8B17F524EA0720 isa PBXBuildFile settings COMPILER_FLAGS -DOS_OBJECT_USE_OBJC=0 7D56DF05F0071D94121A050CDB772D56 fileRef 818AFFCC037C608492FAE59C7C3E85C0 isa PBXBuildFile settings ATTRIBUTES Public 7D8198AD59277CB0379442369CF5247E includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h path MWCaptionView.h sourceTree <group> 7D901800D8324F9F338B54CA286AD771 fileRef D2307EC60BB4D23E49C1420E6F192943 isa PBXBuildFile 7D9E32DAA93E15CFCF77A5BCD2D4BAA2 buildConfigurationList 189B7D1A1C865073D7F999A759ACD292 buildPhases 9C03752CBF78921009132B656CF36184 46A2CE1545DEBE5FE9DF073C8EBFE2F0 6E43863A0284BCD9B517C3AE49207A9D buildRules dependencies isa PBXNativeTarget name FBSnapshotTestCase productName FBSnapshotTestCase productReference D52C083C7341E76797DB059849542613 productType com.apple.product-type.library.static 7DACEAF8582049D6EB8B4CFFB6CA48CD includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name EXPMatchers+beGreaterThan.m path Expecta/Matchers/EXPMatchers+beGreaterThan.m sourceTree <group> 7DB346D0F39D3F0E887471402A8071AB children BA6428E9F66FD5A23C0A2E06ED26CD2F 719353E5F5BDD19B2D80B273F365F2BF F4CDA5FA9197A41E0081E84F932906EB 9D89C03F805626896F95FA71887D578C 9142D907B715CE18385E00AB1972B1BD 29062DA699B861B896758C5A677765BD isa PBXGroup sourceTree <group> 7E05296687CBD4F8C77EA450EEAB7B60 fileRef ABDF044BC66387B0E03E70BAC09D0AC8 isa PBXBuildFile 7FEE0E8D094D7BCCAC7129473EE05ADC fileRef 788D548DE837CA2FA9D2D744D64FA847 isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc 8059E5674B08670B0A002D564FFABF44 fileRef 70922EF029715CA28E814F87373F29C4 isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc 805AE44C01D7936BA8A238C6C5C4807D includeInIndex 1 isa PBXFileReference lastKnownFileType text.xcconfig path Specta.xcconfig sourceTree <group> 809101940909F56A7B70CE17D447640B includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPMatchers+beLessThanOrEqualTo.h path Expecta/Matchers/EXPMatchers+beLessThanOrEqualTo.h sourceTree <group> 8136A5BFC35DA48ABC2C0D94EA79B7F0 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPFloatTuple.h path Expecta/EXPFloatTuple.h sourceTree <group> 818AFFCC037C608492FAE59C7C3E85C0 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name SPTExampleGroup.h path Specta/Specta/SPTExampleGroup.h sourceTree <group> 8261C05543EA69BA5F136A06D5005C50 explicitFileType wrapper.cfbundle includeInIndex 0 isa PBXFileReference name MWPhotoBrowser.bundle path MWPhotoBrowser.bundle sourceTree BUILT_PRODUCTS_DIR 82AC9861A9FE0BEFEC04D31C3B05ABB2 fileRef 40D39B6435EEF47A85F4E3FB684E72FE isa PBXBuildFile settings COMPILER_FLAGS -DOS_OBJECT_USE_OBJC=0 82FC12F67E83874B93592218E9FB4CA0 fileRef 427921605A450AEF67D129DACCD87BED isa PBXBuildFile settings COMPILER_FLAGS -DOS_OBJECT_USE_OBJC=0 8439940A3793CF65B24F4185E04317D8 includeInIndex 1 isa PBXFileReference path UIBarButtonItemGrid@2x.png sourceTree <group> 85A6795A5CD0C6DEA1F648AD0E6D1248 baseConfigurationReference 2055A9E17F10DAA3DD7884DDF96A0A50 buildSettings ENABLE_STRICT_OBJC_MSGSEND YES GCC_PREFIX_HEADER Target Support Files/MWPhotoBrowser/MWPhotoBrowser-prefix.pch IPHONEOS_DEPLOYMENT_TARGET 7.0 MTL_ENABLE_DEBUG_INFO NO OTHER_LDFLAGS OTHER_LIBTOOLFLAGS PRIVATE_HEADERS_FOLDER_PATH PRODUCT_NAME $(TARGET_NAME) PUBLIC_HEADERS_FOLDER_PATH SDKROOT iphoneos SKIP_INSTALL YES isa XCBuildConfiguration name Release 862018A7CC10880108A2BDC5146C8F3F includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPMatchers+beInTheRangeOf.h path Expecta/Matchers/EXPMatchers+beInTheRangeOf.h sourceTree <group> 86334F1A4DF0160C4B098EF643AB6E07 baseConfigurationReference 7389D03E90A74E5A200DD5ADBB209885 buildSettings ENABLE_STRICT_OBJC_MSGSEND YES GCC_PREFIX_HEADER Target Support Files/DACircularProgress/DACircularProgress-prefix.pch IPHONEOS_DEPLOYMENT_TARGET 7.0 MTL_ENABLE_DEBUG_INFO NO OTHER_LDFLAGS OTHER_LIBTOOLFLAGS PRIVATE_HEADERS_FOLDER_PATH PRODUCT_NAME $(TARGET_NAME) PUBLIC_HEADERS_FOLDER_PATH SDKROOT iphoneos SKIP_INSTALL YES isa XCBuildConfiguration name Release 865FAF448207E691D600F40C4ACF7C58 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPUnsupportedObject.h path Expecta/EXPUnsupportedObject.h sourceTree <group> 866569443B7D10AB5D6DC7FC0830235B fileRef DF11024BF511A0D2225D4BA84770CED7 isa PBXBuildFile 86A1ADA0F8A843FA09BFF9142BEF76D5 includeInIndex 1 isa PBXFileReference lastKnownFileType text.xcconfig path Expecta.xcconfig sourceTree <group> 872948DAF79618AD725E0BF364E5DDD4 fileRef 5A06D9F124A077FE3345C713135D7E6C isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc 87A80C8B1F0C635DA56B74807AE426D9 fileRef 2ACE82A15131DC569A786CB49EFD1804 isa PBXBuildFile settings ATTRIBUTES Public 886428740DB164747FBA1935F65900DF buildActionMask 2147483647 files EFE77B95F063C96D9B07BC2A988AB403 B6CB1D947CABAFFCEFC7F94006CB7AD4 319D6926D1261EB1150D83F672CCA35E 00518063767430EDC47FCC49F4409458 4CFCAA497A77475D017F781F8241A4C0 isa PBXFrameworksBuildPhase runOnlyForDeploymentPostprocessing 0 8952D588C0E5AD12FD9CF3747FD061F2 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h path MWTapDetectingView.h sourceTree <group> 8A00437F88A0C89CD672C2085CCCF62B includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name SDWebImageCompat.h path SDWebImage/SDWebImageCompat.h sourceTree <group> 8B2FFB12DD1404D98DCA25509B11611E fileRef 1A1E6F8D4C651A22BB8CCEF1226FB1EE isa PBXBuildFile 8B97E5CA574F06B315C5F1D27B25DEFB fileRef 785A993DF42B3ABEF658B85E1F47663F isa PBXBuildFile 8BEDD694C8600B014302155F48FD5B08 buildConfigurations ECE81C5981477AA7D254ACC70534BE23 BFD984F9AF4C47EDD370C631057D40E8 defaultConfigurationIsVisible 0 defaultConfigurationName Release isa XCConfigurationList 8C4EC29461A90C294949DAE717A1BEC5 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc path EXPMatchers+FBSnapshotTest.m sourceTree <group> 8C4ECAE46CD3BCB39F38F4FF20D074F6 fileRef 0B7E61991D03B44046913BF3B345F4DB isa PBXBuildFile 8D8B7C183B18D82F1958BB3115798433 buildConfigurations C0593046ABD536BD086B85302FA897DF CC01A834786EDBE30D7011502AE0F9B3 defaultConfigurationIsVisible 0 defaultConfigurationName Release isa XCConfigurationList 8DC04A9EEA14F0D2300FB6C0CAEB9CAB includeInIndex 1 isa PBXFileReference path UIBarButtonItemArrowRight@2x.png sourceTree <group> 8DF90D623F7F9015EEE9F1D7FEE7E053 fileRef 0A924BC847BA99EBCBD0588603448B05 isa PBXBuildFile settings ATTRIBUTES Public 8E108520195BF6568A4A53786EA7B6F0 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name FBSnapshotTestController.h path FBSnapshotTestCase/FBSnapshotTestController.h sourceTree <group> 8E485F27A3C78223FB086075C2974A49 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name NSData+ImageContentType.h path SDWebImage/NSData+ImageContentType.h sourceTree <group> 8EE5D8EDB6C595B190E81B76D3190EB3 isa PBXTargetDependency name MBProgressHUD target FE2F1D7B9D9FCEA148517E4657B243F4 targetProxy D7B59E66FA2C765426650ADECEF955EF 8F2B443FA0D511403E4DA2A79DB3302A includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name SPTSharedExampleGroups.m path Specta/Specta/SPTSharedExampleGroups.m sourceTree <group> 8F3064A5B520D1E4290AA240A05B7D6F includeInIndex 1 isa PBXFileReference path VideoOverlay.png sourceTree <group> 8F674582EE71972EE60EFD96C1F173D5 fileRef 0683DF2C9B7FC27B576AB4B7B3B146ED isa PBXBuildFile settings ATTRIBUTES Public 9019F9233E2A8B04A82C1B8D0274F09F fileRef 9A9B45D988FA49FD87F8587C42403EC5 isa PBXBuildFile settings ATTRIBUTES Public 904DB6CCA2AAC8D7B3245ED9628E865A fileRef 691CE6B20E64762C98682B93B31435E7 isa PBXBuildFile settings ATTRIBUTES Public 90C5595E0565CBEBB42EFEBB157FF619 containerPortal D41D8CD98F00B204E9800998ECF8427E isa PBXContainerItemProxy proxyType 1 remoteGlobalIDString DE3B07ED7FF04F506B0C5E00F4973589 remoteInfo SDWebImage 9142D907B715CE18385E00AB1972B1BD children CEFDBDB8900789B0FFB86EBB7065BC62 ADEAC9FE1270035973745D0EB3B84395 6CD36438CB08D280978CEE50363D8B8B D52C083C7341E76797DB059849542613 D7F1441E30E059AC17B4C1CE1464C07B 50F87BFDC26A0B4CC6DE06D3ACBD0714 4D720BB8AE19E427E58843509B28FEB6 E0EBF0357895E0C657A233A9D4D0E2C1 C5284B0E2549CD42E31F469FB8B92DA8 7CF892960748F43245AD2B5CD2AC41A5 8261C05543EA69BA5F136A06D5005C50 isa PBXGroup name Products sourceTree <group> 91E14C244388DE830AA1C4AE88415F76 fileRef BA9181C8B2773795F2EACBDE286994AD isa PBXBuildFile 92F064516748B6AA4C6E303A38C3FE6A includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name EXPExpect.m path Expecta/EXPExpect.m sourceTree <group> 9374BBB3BA16684D1B858CCC9C3E2D8A includeInIndex 1 isa PBXFileReference lastKnownFileType text.plist.xml path Pods-MWPhotoBrowser_Example-acknowledgements.plist sourceTree <group> 93CF7F496E727D827BED2105B65D2073 fileRef E4436D58F27EAC932F3CC2A7619D5FD3 isa PBXBuildFile 94A310E9C2919B9904E9AFBD2837C2B9 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc path MWTapDetectingView.m sourceTree <group> 956FB3AB698AF3DA776A9F24AA79C229 fileRef 66D391F04A8D672C0D42510DE811E7B2 isa PBXBuildFile settings ATTRIBUTES Public 9604D510A96A637DD9AE0D304B68A5A2 includeInIndex 1 isa PBXFileReference path VideoOverlay@3x.png sourceTree <group> 96AE2267633A27498E5D45052BDD5BBA containerPortal D41D8CD98F00B204E9800998ECF8427E isa PBXContainerItemProxy proxyType 1 remoteGlobalIDString 9775596B3CA16905FC8F66DA8E2A3C67 remoteInfo DACircularProgress 9775596B3CA16905FC8F66DA8E2A3C67 buildConfigurationList 619B0B2F7ACD42F8EF700EAE6D1C95DA buildPhases A6993E0B001E6EB572176DFCF9B87C6A F2A9AAC03867BB4990470BAC5912E030 1246A1C00980422020A6884856143651 buildRules dependencies isa PBXNativeTarget name DACircularProgress productName DACircularProgress productReference CEFDBDB8900789B0FFB86EBB7065BC62 productType com.apple.product-type.library.static 97B5429A3CE99BA07086198F10CCE615 containerPortal D41D8CD98F00B204E9800998ECF8427E isa PBXContainerItemProxy proxyType 1 remoteGlobalIDString E2FE87384A67337831E53490EEA8BF11 remoteInfo MWPhotoBrowser 97E91EC237B8623D895DBF6092034AD7 fileRef 862018A7CC10880108A2BDC5146C8F3F isa PBXBuildFile settings ATTRIBUTES Public 981F3EB0C06E97770D2421E673B013A1 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPMatchers+contain.h path Expecta/Matchers/EXPMatchers+contain.h sourceTree <group> 9839B551DAF141B99431C2238C8EEBC0 isa PBXFileReference lastKnownFileType wrapper.framework name AssetsLibrary.framework path Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.0.sdk/System/Library/Frameworks/AssetsLibrary.framework sourceTree DEVELOPER_DIR 990C6F27E60B10F0E4BA0674D80F546B includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h path Expecta-prefix.pch sourceTree <group> 9933B0F6DA01EDF804C19260E9515E65 fileRef 0B7E61991D03B44046913BF3B345F4DB isa PBXBuildFile 9987EAA3314FDCBDC6783C325695E1D8 buildActionMask 2147483647 files E287D744F886B15E1060AFEA475FC727 7E05296687CBD4F8C77EA450EEAB7B60 91E14C244388DE830AA1C4AE88415F76 4DCDDE359E1F3BB419E0F7E9DD12CBB6 BE6668D2527C0063CE1C755190A7596F 7D901800D8324F9F338B54CA286AD771 F26631BB63543446C157972F2F6D1475 FBADF2C12D471003AEB710314DA3FF49 FAC85E18FE868D87FFB2E13C182F4DB9 1E08912939425D011D7C47713E6CDAF0 65F75ED4B34CEDF568441FBC95BA1459 B9051EB6C3E749E2F0538ABF5076D0BE EDC1E77817E2D0252126B98EB0206A62 93CF7F496E727D827BED2105B65D2073 A14C97547283B618AE64053DD92BB395 F2168F9F63056AC8BFB344608C4158BE 8B2FFB12DD1404D98DCA25509B11611E F314DFE6C76A8EEBC3D8CAF03EB89850 5A859FF87C3507C937241DD78BD95EF3 051AA4E51A019933DD1E5CE99CD7352A A2406F04BC69905BAB01C77F8073B55D E99804B4B10C0A5C7C08E5EF962804CE FC1FB976E1C29A75089D2374BDFCCC44 105B37778FEED1965C8D0AF4C9E4B4F1 AB0885F8524D9BE85FDEA8BF30520D43 F88B870D7D0531FB54EAD23EB162DB70 64734F2BA63DAFDA24F819EE71704665 688EFB46F8559BD675FD06384ABA49E1 C1814AA3C422738CF4B8DC63539CCD3E BB3A5C6A66C23862172BF9BED9BC8200 76C42C41A4B61435A3315770F58154B2 D3C785493044AF463085D75F2E9D4FEE E97CDC7A1DFD18DAAD3C505929F32105 isa PBXResourcesBuildPhase runOnlyForDeploymentPostprocessing 0 999D9522F4AD2757D76E2BC0D6606B4A containerPortal D41D8CD98F00B204E9800998ECF8427E isa PBXContainerItemProxy proxyType 1 remoteGlobalIDString 5813251FEA21E4AEE97385709F1912DE remoteInfo MWPhotoBrowser-MWPhotoBrowser 9A086D9BE521AADC7C972AE3D9CD5B48 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name Specta.h path Specta/Specta/Specta.h sourceTree <group> 9A9B45D988FA49FD87F8587C42403EC5 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPMatchers+beSupersetOf.h path Expecta/Matchers/EXPMatchers+beSupersetOf.h sourceTree <group> 9AF3FD4A822DCF644AB50D3967C43C14 buildActionMask 2147483647 files B6B32565B1C26404789EEFCF37B7C225 isa PBXHeadersBuildPhase runOnlyForDeploymentPostprocessing 0 9B546D0F895D9B5A8316B948CEE95C77 fileRef 29E6F040DB8ACF8886F7FE628840C480 isa PBXBuildFile settings ATTRIBUTES Public 9BDA7CB400BA41525D0DAC5E267B2670 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name NSData+ImageContentType.m path SDWebImage/NSData+ImageContentType.m sourceTree <group> 9BF2584F8D336EF737485D7396CFBAFF includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name SpectaDSL.h path Specta/Specta/SpectaDSL.h sourceTree <group> 9C03752CBF78921009132B656CF36184 buildActionMask 2147483647 files 45548268D1B9EE9D37731231D940A591 4463CD014E8C258B7F8BF0FD64901B8F 664FB9826D0F046AB6CE15196CC120B7 A245A84584833A620D85A9C8285C71A1 1186EC1A35306EBBA8C3DE66F0973361 AB5E9DFDA102BB04CB66FC61E01085B0 isa PBXSourcesBuildPhase runOnlyForDeploymentPostprocessing 0 9C8319C4AFAB3CAE3C7314EAD8D1AB34 fileRef F3FAB75D28A2755E4F9F79FB9F2AF5DB isa PBXBuildFile settings COMPILER_FLAGS -DOS_OBJECT_USE_OBJC=0 9C95EBE1D39EEFE5E6404691C95BE2E2 includeInIndex 1 isa PBXFileReference lastKnownFileType text.script.sh path Pods-MWPhotoBrowser_Tests-resources.sh sourceTree <group> 9D5AE460F3B78B02A8AE8E5AD0DCC013 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name EXPMatchers+contain.m path Expecta/Matchers/EXPMatchers+contain.m sourceTree <group> 9D6702575943BF5C71AD0E2299CB53E2 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h path MWPhotoBrowser.h sourceTree <group> 9D6E638E56BDAD3C2196ED9C28FAF857 fileRef E348B132ABCC246013AE3A5532BC609F isa PBXBuildFile settings ATTRIBUTES Public 9D77DAC945EC22411297CBC957C966F0 baseConfigurationReference 86A1ADA0F8A843FA09BFF9142BEF76D5 buildSettings ENABLE_STRICT_OBJC_MSGSEND YES GCC_PREFIX_HEADER Target Support Files/Expecta/Expecta-prefix.pch IPHONEOS_DEPLOYMENT_TARGET 7.0 MTL_ENABLE_DEBUG_INFO NO OTHER_LDFLAGS OTHER_LIBTOOLFLAGS PRIVATE_HEADERS_FOLDER_PATH PRODUCT_NAME $(TARGET_NAME) PUBLIC_HEADERS_FOLDER_PATH SDKROOT iphoneos SKIP_INSTALL YES isa XCBuildConfiguration name Release 9D89C03F805626896F95FA71887D578C children F629BC640170D45C22A8048A42BAB040 F6E655732BA75BED75FFACA472080CC4 505D7B9189B81314EF02A1E9479972D0 A22495C61077EFF6CD10B8B82AFA964B C833942945CF274906A18C8D4BAAE480 2FD7E39AFBAB884FA64CFB5DBDFA1F7B D8BBE4506E8863FF1D2FA4058580F0C8 isa PBXGroup name Pods sourceTree <group> 9E58B396BFA804075E0FCC2D7E764BA6 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name UIImageView+HighlightedWebCache.m path SDWebImage/UIImageView+HighlightedWebCache.m sourceTree <group> 9E907D6C90793FF2CC5533F151827418 containerPortal D41D8CD98F00B204E9800998ECF8427E isa PBXContainerItemProxy proxyType 1 remoteGlobalIDString DE3B07ED7FF04F506B0C5E00F4973589 remoteInfo SDWebImage 9F0508C4938477BDB11EAF1F3A6960E7 isa PBXTargetDependency name FBSnapshotTestCase target 7D9E32DAA93E15CFCF77A5BCD2D4BAA2 targetProxy 5EB6A7FF5E92B3E2E96A92367BE4AA32 9F080C1D1A758D0A874B7345BE67EC81 containerPortal D41D8CD98F00B204E9800998ECF8427E isa PBXContainerItemProxy proxyType 1 remoteGlobalIDString 9775596B3CA16905FC8F66DA8E2A3C67 remoteInfo DACircularProgress 9F1294BB80138FB7DEE240DF61EC1523 buildConfigurations 717B38CC8B651B61F35E12EE4211DAF0 B74838FB9BEB33B6489F58D787E2A6A4 defaultConfigurationIsVisible 0 defaultConfigurationName Release isa XCConfigurationList 9F19BCDE9BF6E330E14588598DFC6337 includeInIndex 1 isa PBXFileReference lastKnownFileType text.plist.xml path Pods-MWPhotoBrowser_Tests-acknowledgements.plist sourceTree <group> A07F4B785B315E5B075218A988F05149 buildActionMask 2147483647 files 060435B6E32CF028837E209B20E10639 isa PBXSourcesBuildPhase runOnlyForDeploymentPostprocessing 0 A09DD0AB6A512D74E5628070EBD56026 isa PBXFileReference lastKnownFileType wrapper.framework name ImageIO.framework path Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.0.sdk/System/Library/Frameworks/ImageIO.framework sourceTree DEVELOPER_DIR A0BA05DFEF00406DC4E8898D36D7418D fileRef A8B86384F8C46CE8544B59FEB6C657F5 isa PBXBuildFile A0C76077A20E75ED950BF4F88A252F83 buildActionMask 2147483647 files 34F30E1314EC4C287E6B89477C2D0FB4 3B332CC633C291A7A96AD0922CFD8AB0 74A9CCFEBE692130454C41959EBA5254 69DC2FB163EAB23712D9C3C13C72518E 0EF1CB072B5DE5D0A4EE7053EB0E2364 C16C9E56CBDDE2AD1FCF3EFFF3127C20 60262C4B68C1B0EF7D3E4FADACB10CD7 518937C0924DA3A3DD216697CED9205F 0498B98EB393D71314953683060EFD58 D791C7B718024D8AED15BE84AA2FAAD0 A14B4E9F94C0A0EC1EE0AE8BFE030787 F9B0A541E338684F945B7539D802CF64 0DC4183837552F23710F86F9A032C574 3939EE53B37025D6F0E05FF8A4C7DCF0 311D5C7B166178CCB79CFD7E8DD4F641 isa PBXHeadersBuildPhase runOnlyForDeploymentPostprocessing 0 A1195ED094D0C8B66BB073959AA2ACCD includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc path MWZoomingScrollView.m sourceTree <group> A14B4E9F94C0A0EC1EE0AE8BFE030787 fileRef 4CFA073D420411E3535ABA966BEE98CC isa PBXBuildFile settings ATTRIBUTES Public A14C97547283B618AE64053DD92BB395 fileRef CB3EA5CC56581FD52BDBA0179D956E7B isa PBXBuildFile A1BF1E794449AF3F514B2EE3C85F4B10 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name FBSnapshotTestCasePlatform.m path FBSnapshotTestCase/FBSnapshotTestCasePlatform.m sourceTree <group> A2118778317DA37C36420E58FB3923B3 buildConfigurationList 5CD9AEB21836919899CDA3A9D9510998 buildPhases F0A8CE7CF766D19972CF0934C1652293 A4AB49CB62EB99071274FD6937EE5352 buildRules dependencies B55362A439917186B971EF580B391A1B 2D5EE41C6DDBA8F159EEA954F335387A 1B030B50B68FB0D602F2E4B1685FA9BA 6D8154E68B3360EE1C8DA3A4443096F3 isa PBXNativeTarget name Pods-MWPhotoBrowser_Example productName Pods-MWPhotoBrowser_Example productReference 4D720BB8AE19E427E58843509B28FEB6 productType com.apple.product-type.library.static A22495C61077EFF6CD10B8B82AFA964B children E348B132ABCC246013AE3A5532BC609F 63327617AD4F38D1236E71968B0991E5 D54993E311EDD8802711B01895286E4E A1BF1E794449AF3F514B2EE3C85F4B10 8E108520195BF6568A4A53786EA7B6F0 0043FDF25F85C0B2C11CEAE6B1C54D87 B0329B325C97BC663835390843F985CE B0DFED38A647950C79B13061F923258C 3B5A7151F8E4FBFE54BF6C2AC80958FA DD5F3D6C4044A157839220B037A055E8 148F0DEDC306B5A6D48931EFD279E456 isa PBXGroup name FBSnapshotTestCase path FBSnapshotTestCase sourceTree <group> A2406F04BC69905BAB01C77F8073B55D fileRef 3EF2F2E1AF840BA0B6AD13D59AA5F80C isa PBXBuildFile A245A84584833A620D85A9C8285C71A1 fileRef 0043FDF25F85C0B2C11CEAE6B1C54D87 isa PBXBuildFile A26F992E8831118311F3DB7CB830595A fileRef FFC94BC21DE6B02A85C4BA9B703D222D isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc A2B692D3F450E25A5D8B17F524EA0720 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name UIImageView+WebCache.m path SDWebImage/UIImageView+WebCache.m sourceTree <group> A4AB49CB62EB99071274FD6937EE5352 buildActionMask 2147483647 files 0364DCB58A865A2830638D2C0CCED41C isa PBXFrameworksBuildPhase runOnlyForDeploymentPostprocessing 0 A59CA631962D8BFBF1D8BA4BA8F1F223 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name SDWebImageDownloader.m path SDWebImage/SDWebImageDownloader.m sourceTree <group> A65C491577A425AF82C53F4A40A0A24B fileRef 1AB26E75F7BCD415D6AD6B8BA5F03DDB isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc A6854D311D55E2BBD8BFCE4E82DF3EA9 fileRef BA9D20AF1E1E4CF225AA2D772049336D isa PBXBuildFile settings ATTRIBUTES Public A6993E0B001E6EB572176DFCF9B87C6A buildActionMask 2147483647 files 5F4AECEF356E35995112F9167B219AD6 82AC9861A9FE0BEFEC04D31C3B05ABB2 EF9006327AFFDBED3FD589DA04E2D36A isa PBXSourcesBuildPhase runOnlyForDeploymentPostprocessing 0 A6A21F67B2A713138BF1A404E3460084 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc path Expecta-dummy.m sourceTree <group> A6FD2A9ACF0632CB41BE9AF1A13BD166 containerPortal D41D8CD98F00B204E9800998ECF8427E isa PBXContainerItemProxy proxyType 1 remoteGlobalIDString 2F501FE84845EAD97B9087DAFCBBEE0E remoteInfo Expecta A7141BC83638F4B38D4D312BAE3BDAC4 fileRef 1BB15402D8C2FB01A198273BA1991DED isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc A815E988348FB8A8B609F012516ABD15 isa PBXTargetDependency name MWPhotoBrowser target E2FE87384A67337831E53490EEA8BF11 targetProxy 97B5429A3CE99BA07086198F10CCE615 A8490A46CB5206BCA5F90FCFBA2D731E fileRef 79996E262256F57DB50CD16E5EBE5D52 isa PBXBuildFile settings ATTRIBUTES Public A8B86384F8C46CE8544B59FEB6C657F5 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc path MWGridViewController.m sourceTree <group> AA7B402D31D86AE5E3DD083408311FF1 fileRef E4AD00C4B132C99837C22CC9031DE9F8 isa PBXBuildFile settings ATTRIBUTES Public AABA7597E19FD75C9F320BCD6BB2759E includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc path MWPhotoBrowser.m sourceTree <group> AAE75938ED3DD46BC00352B82D7CA890 fileRef B2CBEFFA54424B8FBBE1EB23EC879E69 isa PBXBuildFile settings ATTRIBUTES Public AAF71AC633AFEC1D271F81DA183E75F0 fileRef 9A086D9BE521AADC7C972AE3D9CD5B48 isa PBXBuildFile settings ATTRIBUTES Public AB0885F8524D9BE85FDEA8BF30520D43 fileRef BDD78DD77E210708F222D40A2FDE75B1 isa PBXBuildFile AB1D6408D48F6ECF3FCE553BE73961F5 fileRef 2E7CBF297AA8A0E93C23BFAF1BE94DEE isa PBXBuildFile settings ATTRIBUTES Public AB5E9DFDA102BB04CB66FC61E01085B0 fileRef DD5F3D6C4044A157839220B037A055E8 isa PBXBuildFile AB8960E6933BD274C11AD9C15CF410AF includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPMatchers+haveCountOf.h path Expecta/Matchers/EXPMatchers+haveCountOf.h sourceTree <group> ABDF044BC66387B0E03E70BAC09D0AC8 includeInIndex 1 isa PBXFileReference path ImageError@2x.png sourceTree <group> AD198F3513AFD6128A50C356DC253545 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc path Specta-dummy.m sourceTree <group> AD6791D14732A3C17164F61CC72FFB0D fileRef AE917511985F6891AF3F8233B9D34001 isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc ADEAC9FE1270035973745D0EB3B84395 explicitFileType archive.ar includeInIndex 0 isa PBXFileReference name libExpecta.a path libExpecta.a sourceTree BUILT_PRODUCTS_DIR AE1B7431EAFB4D89A49A9DAD35DBE790 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name UIImageView+WebCache.h path SDWebImage/UIImageView+WebCache.h sourceTree <group> AE6C39B87AE391EC710DA8FB520ED3F5 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h path MWZoomingScrollView.h sourceTree <group> AE917511985F6891AF3F8233B9D34001 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name EXPMatchers+beGreaterThanOrEqualTo.m path Expecta/Matchers/EXPMatchers+beGreaterThanOrEqualTo.m sourceTree <group> AF944A6486FDAD944BE766A70129E6D2 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc path DACircularProgress-dummy.m sourceTree <group> B0329B325C97BC663835390843F985CE includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name UIImage+Compare.h path FBSnapshotTestCase/UIImage+Compare.h sourceTree <group> B0D25391E290ADE1BF90E18B8281F5F3 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name EXPMatchers+beFalsy.m path Expecta/Matchers/EXPMatchers+beFalsy.m sourceTree <group> B0DFED38A647950C79B13061F923258C includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name UIImage+Compare.m path FBSnapshotTestCase/UIImage+Compare.m sourceTree <group> B1A8AF86363A5D8093FFE48748DC20A1 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc path SDWebImage-dummy.m sourceTree <group> B23D9E49B5B3A5C76EAFFC9C7F40B4B9 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name EXPUnsupportedObject.m path Expecta/EXPUnsupportedObject.m sourceTree <group> B25B4A401CE0322E5FA936642D8E8A36 fileRef 7D8198AD59277CB0379442369CF5247E isa PBXBuildFile settings ATTRIBUTES Public B28719F6B1978267C407032A2AE646F8 fileRef FE9D7A964178EF6E3CE5FC52134DF825 isa PBXBuildFile settings COMPILER_FLAGS -DOS_OBJECT_USE_OBJC=0 B2CBEFFA54424B8FBBE1EB23EC879E69 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPMatchers+raise.h path Expecta/Matchers/EXPMatchers+raise.h sourceTree <group> B2DE78D2D5A5CF173FEBFA2FE4B4023D fileRef 2F29BE2EDEBA2861368726A4772F4E8E isa PBXBuildFile settings ATTRIBUTES Public B33234F432A72D5E8B65694AE937B78F fileRef 56BD615DEEA3BC153C0B73BAC8A60A13 isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc B37F0F91F85060E28F1DAAB522DC7EC1 buildSettings ALWAYS_SEARCH_USER_PATHS NO CLANG_CXX_LANGUAGE_STANDARD gnu++0x CLANG_CXX_LIBRARY libc++ CLANG_ENABLE_MODULES YES CLANG_ENABLE_OBJC_ARC YES CLANG_WARN_BOOL_CONVERSION YES CLANG_WARN_CONSTANT_CONVERSION YES CLANG_WARN_DIRECT_OBJC_ISA_USAGE YES CLANG_WARN_EMPTY_BODY YES CLANG_WARN_ENUM_CONVERSION YES CLANG_WARN_INT_CONVERSION YES CLANG_WARN_OBJC_ROOT_CLASS YES CLANG_WARN_UNREACHABLE_CODE YES CLANG_WARN__DUPLICATE_METHOD_MATCH YES COPY_PHASE_STRIP NO GCC_C_LANGUAGE_STANDARD gnu99 GCC_DYNAMIC_NO_PIC NO GCC_OPTIMIZATION_LEVEL 0 GCC_PREPROCESSOR_DEFINITIONS DEBUG=1 $(inherited) GCC_SYMBOLS_PRIVATE_EXTERN NO GCC_WARN_64_TO_32_BIT_CONVERSION YES GCC_WARN_ABOUT_RETURN_TYPE YES GCC_WARN_UNDECLARED_SELECTOR YES GCC_WARN_UNINITIALIZED_AUTOS YES GCC_WARN_UNUSED_FUNCTION YES GCC_WARN_UNUSED_VARIABLE YES IPHONEOS_DEPLOYMENT_TARGET 7.0 ONLY_ACTIVE_ARCH YES STRIP_INSTALLED_PRODUCT NO SYMROOT ${SRCROOT}/../build isa XCBuildConfiguration name Debug B52658FC373BA42C6DBF475CAEA30F09 fileRef 9D6702575943BF5C71AD0E2299CB53E2 isa PBXBuildFile settings ATTRIBUTES Public B55362A439917186B971EF580B391A1B isa PBXTargetDependency name DACircularProgress target 9775596B3CA16905FC8F66DA8E2A3C67 targetProxy 9F080C1D1A758D0A874B7345BE67EC81 B5969E17FAFFDC548233DC602AAC6211 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPMatchers+beGreaterThanOrEqualTo.h path Expecta/Matchers/EXPMatchers+beGreaterThanOrEqualTo.h sourceTree <group> B6B32565B1C26404789EEFCF37B7C225 fileRef 3540CB186FE2EE9EE99381DA600738B8 isa PBXBuildFile settings ATTRIBUTES Public B6CB1D947CABAFFCEFC7F94006CB7AD4 fileRef DF11024BF511A0D2225D4BA84770CED7 isa PBXBuildFile B6CE0371E70FB931CC99A90CEBFD6E83 containerPortal D41D8CD98F00B204E9800998ECF8427E isa PBXContainerItemProxy proxyType 1 remoteGlobalIDString E2FE87384A67337831E53490EEA8BF11 remoteInfo MWPhotoBrowser B733330A3935B54DE57E0503C1867DAE fileRef AD198F3513AFD6128A50C356DC253545 isa PBXBuildFile B73F44C64FD91479CFE63C882F1E5E70 children CB09954F9727813F76DD0EC906DB781E B1A8AF86363A5D8093FFE48748DC20A1 DD16771750E90A2599B84EDB4AFF9B9E isa PBXGroup name Support Files path ../Target Support Files/SDWebImage sourceTree <group> B74838FB9BEB33B6489F58D787E2A6A4 baseConfigurationReference 2055A9E17F10DAA3DD7884DDF96A0A50 buildSettings ENABLE_STRICT_OBJC_MSGSEND YES PRODUCT_NAME MWPhotoBrowser SDKROOT iphoneos SKIP_INSTALL YES WRAPPER_EXTENSION bundle isa XCBuildConfiguration name Release B83D4C05E77991780CA69BF68B431DD2 includeInIndex 1 isa PBXFileReference lastKnownFileType text.xcconfig path Pods-MWPhotoBrowser_Example.release.xcconfig sourceTree <group> B8FC063009C06CE659C8CF168A9F8765 fileRef 0B7E61991D03B44046913BF3B345F4DB isa PBXBuildFile B9051EB6C3E749E2F0538ABF5076D0BE fileRef 60E1461AAED23D358B1A25A48F9DD77F isa PBXBuildFile B98B98DB394C072483E8066C6B594401 buildActionMask 2147483647 files 589CDFB90C98BF58F34B688A2BEF2D04 1181CB6FC2D63C42F19EAB56C604A71E isa PBXFrameworksBuildPhase runOnlyForDeploymentPostprocessing 0 BA4C6CCE8BCEAFA07BB2679C87A008C5 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h path MWPhotoBrowser-prefix.pch sourceTree <group> BA6428E9F66FD5A23C0A2E06ED26CD2F includeInIndex 1 isa PBXFileReference lastKnownFileType text name Podfile path ../Podfile sourceTree SOURCE_ROOT xcLanguageSpecificationIdentifier xcode.lang.ruby BA9181C8B2773795F2EACBDE286994AD includeInIndex 1 isa PBXFileReference path ImageError@3x.png sourceTree <group> BA9D20AF1E1E4CF225AA2D772049336D includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name Expecta.h path Expecta/Expecta.h sourceTree <group> BB3A5C6A66C23862172BF9BED9BC8200 fileRef D64041D7A1004D2C8771C29362B59366 isa PBXBuildFile BD30B724A71CF5D6E93805B7615EC79C fileRef 0AB1B49E98BEDDFC8A45BB3C1FB54076 isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc BD4E094BE48FCAB50C0CEFC92B2C0B04 includeInIndex 1 isa PBXFileReference path ImageSelectedSmallOff.png sourceTree <group> BD56223A67203315C1B15E39E3B40934 includeInIndex 1 isa PBXFileReference lastKnownFileType text.script.sh path Pods-MWPhotoBrowser_Tests-frameworks.sh sourceTree <group> BDD78DD77E210708F222D40A2FDE75B1 includeInIndex 1 isa PBXFileReference path UIBarButtonItemArrowRight.png sourceTree <group> BE6668D2527C0063CE1C755190A7596F fileRef E6798A1D89E725C8EB02146EE4B0E31E isa PBXBuildFile BEEAFC79B66B12E6D67D140381BE25F2 fileRef 5A531485C6A0D185C72E03CD91B49919 isa PBXBuildFile settings ATTRIBUTES Public BFD984F9AF4C47EDD370C631057D40E8 baseConfigurationReference 1580BE8D870658FFFB9EF3D04B9213CC buildSettings ENABLE_STRICT_OBJC_MSGSEND YES IPHONEOS_DEPLOYMENT_TARGET 7.0 MACH_O_TYPE staticlib MTL_ENABLE_DEBUG_INFO NO OTHER_LDFLAGS OTHER_LIBTOOLFLAGS PODS_ROOT $(SRCROOT) PRODUCT_NAME $(TARGET_NAME) SDKROOT iphoneos SKIP_INSTALL YES isa XCBuildConfiguration name Release C0593046ABD536BD086B85302FA897DF baseConfigurationReference CB09954F9727813F76DD0EC906DB781E buildSettings ENABLE_STRICT_OBJC_MSGSEND YES GCC_PREFIX_HEADER Target Support Files/SDWebImage/SDWebImage-prefix.pch IPHONEOS_DEPLOYMENT_TARGET 7.0 MTL_ENABLE_DEBUG_INFO YES OTHER_LDFLAGS OTHER_LIBTOOLFLAGS PRIVATE_HEADERS_FOLDER_PATH PRODUCT_NAME $(TARGET_NAME) PUBLIC_HEADERS_FOLDER_PATH SDKROOT iphoneos SKIP_INSTALL YES isa XCBuildConfiguration name Debug C0A33FBAF4E7C28807891EC8D0B65204 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc path MWPhoto.m sourceTree <group> C162177585DC8E186D24CADCC233B0EA includeInIndex 1 isa PBXFileReference path UIBarButtonItemGrid.png sourceTree <group> C16C9E56CBDDE2AD1FCF3EFFF3127C20 fileRef 47BC96C02F9C3E09817E3FEC2559E33B isa PBXBuildFile settings ATTRIBUTES Public C1814AA3C422738CF4B8DC63539CCD3E fileRef 8439940A3793CF65B24F4185E04317D8 isa PBXBuildFile C2420EB9004B4667C6328B5C0D098C48 fileRef 49D43B7D98ABA386628BDA9FB6BA411B isa PBXBuildFile settings ATTRIBUTES Public C266061F84E4CFB93AF99D21EEB13401 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h path MWPhotoBrowserPrivate.h sourceTree <group> C2909BC6E2528AB50ADD2B0E4FE78DA1 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name SPTTestSuite.m path Specta/Specta/SPTTestSuite.m sourceTree <group> C2BFF99EB859FD7056CF72C4850693D1 fileRef 63010FB3922AB3E2DDD6D8CDD2D10BFA isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc C2CEB9C1175C4FC4D5BE5C6EF285E0A4 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name EXPMatchers+beTruthy.m path Expecta/Matchers/EXPMatchers+beTruthy.m sourceTree <group> C36BB9623ECA2B27942B66986A784EC9 includeInIndex 1 isa PBXFileReference path UIBarButtonItemArrowLeft@2x.png sourceTree <group> C3D6913F5E4E594A29E40A526602BBB7 isa PBXTargetDependency name MWPhotoBrowser-MWPhotoBrowser target 5813251FEA21E4AEE97385709F1912DE targetProxy 999D9522F4AD2757D76E2BC0D6606B4A C402F1446422CAB85FC53BD77E5E9018 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h path MWGridCell.h sourceTree <group> C5284B0E2549CD42E31F469FB8B92DA8 explicitFileType archive.ar includeInIndex 0 isa PBXFileReference name libSDWebImage.a path libSDWebImage.a sourceTree BUILT_PRODUCTS_DIR C5AFFFCB54FD9036E7BC9BA54ADAAF30 baseConfigurationReference 0557FD60A45F62CD51B4972ECE5C2584 buildSettings ENABLE_STRICT_OBJC_MSGSEND YES GCC_PREFIX_HEADER Target Support Files/MBProgressHUD/MBProgressHUD-prefix.pch IPHONEOS_DEPLOYMENT_TARGET 7.0 MTL_ENABLE_DEBUG_INFO NO OTHER_LDFLAGS OTHER_LIBTOOLFLAGS PRIVATE_HEADERS_FOLDER_PATH PRODUCT_NAME $(TARGET_NAME) PUBLIC_HEADERS_FOLDER_PATH SDKROOT iphoneos SKIP_INSTALL YES isa XCBuildConfiguration name Release C5BD0A3729BF33E203724A88CFE727BC fileRef FCA01B035C5FC9AF0969977B2C54A7A9 isa PBXBuildFile C655D4974F9982F0810618C9AC07216F includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name DALabeledCircularProgressView.m path DACircularProgress/DALabeledCircularProgressView.m sourceTree <group> C6671739E8C5904113586F8BEBBC9780 fileRef F40AD0DE1188AA331D98100B8766D6C8 isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc C6AD7852D34E8A80DFF3B8376BDE812F buildActionMask 2147483647 files 8DF90D623F7F9015EEE9F1D7FEE7E053 6A4E3ACA285A21392936C110E7EC91F0 3A013F13122CDB8EE962F8CB7C6FCC8E A6854D311D55E2BBD8BFCE4E82DF3EA9 081F2104425CDCB0915354E2FBD7E24E 7AC91F55DAAA2F0223A97BEFF8BCAF68 2A17721E4A81DB608CA6D4FB6F0ADAFB 341D7536159B52F41598F730CC45D548 07282695806D1DFBF187BFA004D80641 A8490A46CB5206BCA5F90FCFBA2D731E AA7B402D31D86AE5E3DD083408311FF1 F4CA468B5A9F8FF2A4DB8B236A8E71BF 021C50274FF43A6F07E119D572C3ACB6 8F674582EE71972EE60EFD96C1F173D5 3E463E2B6917D9AA08A03BA8A8E74A18 AB1D6408D48F6ECF3FCE553BE73961F5 D42799488F38F2DB8A580730CEE13CE6 97E91EC237B8623D895DBF6092034AD7 9B546D0F895D9B5A8316B948CEE95C77 DFF580AE359407E841BA8D8DDCE6E299 EE52A320EC3155B114104E06396D1B59 74707D5ABEC55B3084F52C40A4227B06 14C609D8F203FD45194E93997EFF744E 9019F9233E2A8B04A82C1B8D0274F09F 70545E4EA86C6E593A2A9F6731DA8F6D 2945FAA75C956DD6A541EB51E42E6899 02C7E3EC1E1DFDD7046BD26A67E92686 76331E71086C8CD5118A69B046D8F0FB 430CE433DB59FE090A8CC6AFCFA43337 237FB063FB365119546C7B5006224F3B C98F5401E5C1AB6512BE50C3B7CEA9BF 339A0C1BFF72397A705959E03877DDDB AAE75938ED3DD46BC00352B82D7CA890 6EB2498C2AFB1DF8555CB7C1EF89CA5C E867CBF850D20C314BF4BD790432455D 6924E116731D7079958063A3EE0781ED 4231743B6C143BDB4A5FBB032E6D3799 956FB3AB698AF3DA776A9F24AA79C229 60D3CCEB5B53542228790ABD8885AF42 isa PBXHeadersBuildPhase runOnlyForDeploymentPostprocessing 0 C6D58082433E54F547A343BCED70CA50 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h path Specta-prefix.pch sourceTree <group> C6FE69AE06A770FF76C19B8039EC0780 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc path Pods-MWPhotoBrowser_Tests-dummy.m sourceTree <group> C77F8C54B4042EC0746E5D285E7D400A includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name SDWebImageDecoder.h path SDWebImage/SDWebImageDecoder.h sourceTree <group> C833942945CF274906A18C8D4BAAE480 children EF4D256729962DC82677BF90E861294B 427921605A450AEF67D129DACCD87BED 1B0ADBF8A991262A6DE5BD0CB101EEE0 isa PBXGroup name MBProgressHUD path MBProgressHUD sourceTree <group> C98F5401E5C1AB6512BE50C3B7CEA9BF fileRef 79D73FB4B6A3C92513B81243EA72E78C isa PBXBuildFile settings ATTRIBUTES Public C9B5F86F6244D78D3F80384F609427C6 containerPortal D41D8CD98F00B204E9800998ECF8427E isa PBXContainerItemProxy proxyType 1 remoteGlobalIDString 2F501FE84845EAD97B9087DAFCBBEE0E remoteInfo Expecta C9B97F514121309E4531C3439E206EC7 includeInIndex 1 isa PBXFileReference lastKnownFileType text.script.sh path Pods-MWPhotoBrowser_Example-resources.sh sourceTree <group> C9BF70FB69B8740763B185C23C560434 includeInIndex 1 isa PBXFileReference path ImageSelectedOn@3x.png sourceTree <group> CA0DFA401B2A45337DFC72F893A060EF fileRef 523087C6D186A9F5BEE78D39F9A78F3F isa PBXBuildFile CA8230DE5C83368629EF3E62C4F0E7B7 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name XCTestCase+Specta.m path Specta/Specta/XCTestCase+Specta.m sourceTree <group> CB08C9C83ABDBE55762A423ED48491EF fileRef 7DACEAF8582049D6EB8B4CFFB6CA48CD isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc CB09954F9727813F76DD0EC906DB781E includeInIndex 1 isa PBXFileReference lastKnownFileType text.xcconfig path SDWebImage.xcconfig sourceTree <group> CB3EA5CC56581FD52BDBA0179D956E7B includeInIndex 1 isa PBXFileReference path ImageSelectedSmallOn@3x.png sourceTree <group> CB7148E744A9D03AEDC9D4809248999F baseConfigurationReference 7389D03E90A74E5A200DD5ADBB209885 buildSettings ENABLE_STRICT_OBJC_MSGSEND YES GCC_PREFIX_HEADER Target Support Files/DACircularProgress/DACircularProgress-prefix.pch IPHONEOS_DEPLOYMENT_TARGET 7.0 MTL_ENABLE_DEBUG_INFO YES OTHER_LDFLAGS OTHER_LIBTOOLFLAGS PRIVATE_HEADERS_FOLDER_PATH PRODUCT_NAME $(TARGET_NAME) PUBLIC_HEADERS_FOLDER_PATH SDKROOT iphoneos SKIP_INSTALL YES isa XCBuildConfiguration name Debug CBE0BF23E2B3F39D22B51BC93022DE7D children 8E485F27A3C78223FB086075C2974A49 9BDA7CB400BA41525D0DAC5E267B2670 2D6EC33CA8D9BD953AD29E16068F2CBD DE369A67A17A1EE2351376730BB039A4 8A00437F88A0C89CD672C2085CCCF62B 10E446637EC5FF03088009E61B491E75 C77F8C54B4042EC0746E5D285E7D400A 6C982719FDCF6684EB752A0153066FC3 574FFB37A8EF309B1A1EB265267DD353 A59CA631962D8BFBF1D8BA4BA8F1F223 47BC96C02F9C3E09817E3FEC2559E33B 7599165E41821009340AA99FDAC29AE0 222A85D6DAB7D3AC98EF6D7E68B13740 2EDD6193448F7939784FDF1C8016F000 D615D83AA6E624C9EEFAB11B6DAB4612 EC0223776620A3EA741C855D0211CB0A 78759DF877B83416A90AA2BC606BF7ED 518626B93DA6269F02901D111B4DBF26 661962DFFB8F4AF681F8EA78582EE534 4CFA073D420411E3535ABA966BEE98CC 6B1C830074F8BFC3AE3CB3A5278754A8 1A1E8E8494B9BFCEBBD8273148D44386 FE9D7A964178EF6E3CE5FC52134DF825 5D39CEB65ADB521B117CA181C2BB2C9C 9E58B396BFA804075E0FCC2D7E764BA6 AE1B7431EAFB4D89A49A9DAD35DBE790 A2B692D3F450E25A5D8B17F524EA0720 D91F4635A8EDF54921FC9B759BE009F6 38777F32EB3F13ED8D2047C1C9ED6D6D isa PBXGroup name Core sourceTree <group> CC01A834786EDBE30D7011502AE0F9B3 baseConfigurationReference CB09954F9727813F76DD0EC906DB781E buildSettings ENABLE_STRICT_OBJC_MSGSEND YES GCC_PREFIX_HEADER Target Support Files/SDWebImage/SDWebImage-prefix.pch IPHONEOS_DEPLOYMENT_TARGET 7.0 MTL_ENABLE_DEBUG_INFO NO OTHER_LDFLAGS OTHER_LIBTOOLFLAGS PRIVATE_HEADERS_FOLDER_PATH PRODUCT_NAME $(TARGET_NAME) PUBLIC_HEADERS_FOLDER_PATH SDKROOT iphoneos SKIP_INSTALL YES isa XCBuildConfiguration name Release CD12BF87A360D901CFA6AC563638FED1 children 805AE44C01D7936BA8A238C6C5C4807D AD198F3513AFD6128A50C356DC253545 C6D58082433E54F547A343BCED70CA50 isa PBXGroup name Support Files path ../Target Support Files/Specta sourceTree <group> CEFDBDB8900789B0FFB86EBB7065BC62 explicitFileType archive.ar includeInIndex 0 isa PBXFileReference name libDACircularProgress.a path libDACircularProgress.a sourceTree BUILT_PRODUCTS_DIR CF37F67F6269058934EB779D902EE7AB fileRef C0A33FBAF4E7C28807891EC8D0B65204 isa PBXBuildFile CF95446EA555B49150EA7270096D78B2 fileRef E906DDCC926C2EA5161E326AB04AC53F isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc D041545E1DB9D43FCBB294B7E9573AFF fileRef DF11024BF511A0D2225D4BA84770CED7 isa PBXBuildFile D09A95F4452843967F4C8F3FA5EC1A68 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name ExpectaSupport.h path Expecta/ExpectaSupport.h sourceTree <group> D10BA2216A9B5F41557B4FC448EE865C includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPMatchers+equal.h path Expecta/Matchers/EXPMatchers+equal.h sourceTree <group> D2307EC60BB4D23E49C1420E6F192943 includeInIndex 1 isa PBXFileReference path ImageSelectedOff@3x.png sourceTree <group> D351C08D4390F427D8E1BEC3CB8A57BD children 9839B551DAF141B99431C2238C8EEBC0 DB7D9B8599935A5FF3862B90E049CABD DF11024BF511A0D2225D4BA84770CED7 A09DD0AB6A512D74E5628070EBD56026 6CD3EC2C1FBF5655137A99638E9CC950 5E350F03F362C03BE32737DDE72D4632 0B7E61991D03B44046913BF3B345F4DB isa PBXGroup name iOS sourceTree <group> D359D2CDD896874DAD7B6D2EF3FEE26D isa PBXTargetDependency name SDWebImage target DE3B07ED7FF04F506B0C5E00F4973589 targetProxy 9E907D6C90793FF2CC5533F151827418 D3C785493044AF463085D75F2E9D4FEE fileRef 2DE374580729793C277006A3481402A3 isa PBXBuildFile D41D8CD98F00B204E9800998ECF8427E attributes LastSwiftUpdateCheck 0700 LastUpgradeCheck 0700 buildConfigurationList 2D8E8EC45A3A1A1D94AE762CB5028504 compatibilityVersion Xcode 3.2 developmentRegion English hasScannedForEncodings 0 isa PBXProject knownRegions en mainGroup 7DB346D0F39D3F0E887471402A8071AB productRefGroup 9142D907B715CE18385E00AB1972B1BD projectDirPath projectReferences projectRoot targets 9775596B3CA16905FC8F66DA8E2A3C67 2F501FE84845EAD97B9087DAFCBBEE0E 05257CC76400D71826E603561A0F73E6 7D9E32DAA93E15CFCF77A5BCD2D4BAA2 FE2F1D7B9D9FCEA148517E4657B243F4 E2FE87384A67337831E53490EEA8BF11 5813251FEA21E4AEE97385709F1912DE A2118778317DA37C36420E58FB3923B3 722090AAE01BA442E883E6F02AA8DF40 DE3B07ED7FF04F506B0C5E00F4973589 E9AAC3590B391F4CFAF5F8348E6A8FE4 D42799488F38F2DB8A580730CEE13CE6 fileRef 3C5A51B8AE7322A210CF01AFBA648598 isa PBXBuildFile settings ATTRIBUTES Public D47B0B4C04222F6784D38356F02BEB32 containerPortal D41D8CD98F00B204E9800998ECF8427E isa PBXContainerItemProxy proxyType 1 remoteGlobalIDString FE2F1D7B9D9FCEA148517E4657B243F4 remoteInfo MBProgressHUD D52C083C7341E76797DB059849542613 explicitFileType archive.ar includeInIndex 0 isa PBXFileReference name libFBSnapshotTestCase.a path libFBSnapshotTestCase.a sourceTree BUILT_PRODUCTS_DIR D54993E311EDD8802711B01895286E4E includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name FBSnapshotTestCasePlatform.h path FBSnapshotTestCase/FBSnapshotTestCasePlatform.h sourceTree <group> D583D511354F609B73EB28F4CFA2C32A isa PBXTargetDependency name DACircularProgress target 9775596B3CA16905FC8F66DA8E2A3C67 targetProxy 386634C7797B60DF71C8AD13DAAD315D D5DEC16E7CEEFEABBFEBAC3A10358C5A includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h path FBSnapshotTestCase-prefix.pch sourceTree <group> D5EE5F7C5A846B01ACF225DE04E52196 includeInIndex 1 isa PBXFileReference lastKnownFileType text.xcconfig path Pods-MWPhotoBrowser_Example.debug.xcconfig sourceTree <group> D615D83AA6E624C9EEFAB11B6DAB4612 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name SDWebImageOperation.h path SDWebImage/SDWebImageOperation.h sourceTree <group> D64041D7A1004D2C8771C29362B59366 includeInIndex 1 isa PBXFileReference path UIBarButtonItemGrid@3x.png sourceTree <group> D68BA9A93FD415E751DFA95CCF7DCB8D includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name EXPMatchers+beCloseTo.m path Expecta/Matchers/EXPMatchers+beCloseTo.m sourceTree <group> D70BED84191514A998A08D7F3B8BBB17 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name EXPDoubleTuple.m path Expecta/EXPDoubleTuple.m sourceTree <group> D791C7B718024D8AED15BE84AA2FAAD0 fileRef 518626B93DA6269F02901D111B4DBF26 isa PBXBuildFile settings ATTRIBUTES Public D7A0524D2A055F825B4DF69A9DBBFF98 fileRef A1195ED094D0C8B66BB073959AA2ACCD isa PBXBuildFile D7B59E66FA2C765426650ADECEF955EF containerPortal D41D8CD98F00B204E9800998ECF8427E isa PBXContainerItemProxy proxyType 1 remoteGlobalIDString FE2F1D7B9D9FCEA148517E4657B243F4 remoteInfo MBProgressHUD D7F1441E30E059AC17B4C1CE1464C07B explicitFileType archive.ar includeInIndex 0 isa PBXFileReference name libMBProgressHUD.a path libMBProgressHUD.a sourceTree BUILT_PRODUCTS_DIR D8619A771228AD0402C4D464E3C6EE53 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h path MWPhoto.h sourceTree <group> D8BBE4506E8863FF1D2FA4058580F0C8 children 9A086D9BE521AADC7C972AE3D9CD5B48 9BF2584F8D336EF737485D7396CFBAFF F3FAB75D28A2755E4F9F79FB9F2AF5DB 6347B71E8C22BA00F73AB093F101B7D4 3CD608194F35F47CEC250368B43C48E9 DBE203B988A948E71FDDCAE62BA1F4CC 2ACE82A15131DC569A786CB49EFD1804 0668957FEB1B3CAE2EABBC4AA9ABA7C3 569060A075537DFE6A4D25D96C98668B 56228B3CBF246784930BFE7B29EFDD04 2F29BE2EDEBA2861368726A4772F4E8E 1AFDF0F0EF2A314336EACA38FCC6F784 818AFFCC037C608492FAE59C7C3E85C0 571096112AA1AD00C10596D216783599 1D772DC880BCFABF5028DA167DE6B119 49D43B7D98ABA386628BDA9FB6BA411B 1398D1B84667A17FCB66182F776EAC42 8F2B443FA0D511403E4DA2A79DB3302A 691CE6B20E64762C98682B93B31435E7 F1948E226356BF652752F9FA7317BC73 EF75C10714B7CFF9071553F07D94A550 C2909BC6E2528AB50ADD2B0E4FE78DA1 1EF011C5AA1726984A1B50A46191CE9B 1E360C94C47826C4CAEEDBFC04C36A42 CA8230DE5C83368629EF3E62C4F0E7B7 CD12BF87A360D901CFA6AC563638FED1 isa PBXGroup name Specta path Specta sourceTree <group> D91F4635A8EDF54921FC9B759BE009F6 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name UIView+WebCacheOperation.h path SDWebImage/UIView+WebCacheOperation.h sourceTree <group> D9E225250B8DAA16A4D7B6A66EC6574A fileRef C2909BC6E2528AB50ADD2B0E4FE78DA1 isa PBXBuildFile settings COMPILER_FLAGS -DOS_OBJECT_USE_OBJC=0 DA7F16221783A03C084D9BAAF7F404DB includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPMatchers+beginWith.h path Expecta/Matchers/EXPMatchers+beginWith.h sourceTree <group> DB7D9B8599935A5FF3862B90E049CABD isa PBXFileReference lastKnownFileType wrapper.framework name CoreGraphics.framework path Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.0.sdk/System/Library/Frameworks/CoreGraphics.framework sourceTree DEVELOPER_DIR DBE203B988A948E71FDDCAE62BA1F4CC includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name SpectaUtility.m path Specta/Specta/SpectaUtility.m sourceTree <group> DC1F1DBD53679CB78C5967BA9DEC08CA fileRef 78759DF877B83416A90AA2BC606BF7ED isa PBXBuildFile settings COMPILER_FLAGS -DOS_OBJECT_USE_OBJC=0 DD16771750E90A2599B84EDB4AFF9B9E includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h path SDWebImage-prefix.pch sourceTree <group> DD5F3D6C4044A157839220B037A055E8 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name UIImage+Diff.m path FBSnapshotTestCase/UIImage+Diff.m sourceTree <group> DD7BDEE57A8E95F9F327AC87B6A78256 fileRef C402F1446422CAB85FC53BD77E5E9018 isa PBXBuildFile settings ATTRIBUTES Public DDAA00E6C2E81A6C35534DD632143F09 children 7389D03E90A74E5A200DD5ADBB209885 AF944A6486FDAD944BE766A70129E6D2 E5EA159155EA435901485FBB8111088C isa PBXGroup name Support Files path ../Target Support Files/DACircularProgress sourceTree <group> DE0797009C8330FFDB1FC0EB8D2DA9DD buildActionMask 2147483647 files isa PBXSourcesBuildPhase runOnlyForDeploymentPostprocessing 0 DE369A67A17A1EE2351376730BB039A4 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name SDImageCache.m path SDWebImage/SDImageCache.m sourceTree <group> DE3B07ED7FF04F506B0C5E00F4973589 buildConfigurationList 8D8B7C183B18D82F1958BB3115798433 buildPhases 7357CD98F48585C602A75C901AD5E096 B98B98DB394C072483E8066C6B594401 A0C76077A20E75ED950BF4F88A252F83 buildRules dependencies isa PBXNativeTarget name SDWebImage productName SDWebImage productReference C5284B0E2549CD42E31F469FB8B92DA8 productType com.apple.product-type.library.static DF11024BF511A0D2225D4BA84770CED7 isa PBXFileReference lastKnownFileType wrapper.framework name Foundation.framework path Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.0.sdk/System/Library/Frameworks/Foundation.framework sourceTree DEVELOPER_DIR DFA142DA2A1E6AB454178652BAC9A075 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPDoubleTuple.h path Expecta/EXPDoubleTuple.h sourceTree <group> DFAB389ECAD69BF8BE6357CFADABBF5C fileRef 9E58B396BFA804075E0FCC2D7E764BA6 isa PBXBuildFile settings COMPILER_FLAGS -DOS_OBJECT_USE_OBJC=0 DFF580AE359407E841BA8D8DDCE6E299 fileRef ED1EC819236EDC3B337FCB044454FEB6 isa PBXBuildFile settings ATTRIBUTES Public E06376455C1D5E45B97ACDC5438FC15B fileRef 40DAD22BCDD38D058B1C9A312C42DF49 isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc E0EBF0357895E0C657A233A9D4D0E2C1 explicitFileType archive.ar includeInIndex 0 isa PBXFileReference name libPods-MWPhotoBrowser_Tests.a path libPods-MWPhotoBrowser_Tests.a sourceTree BUILT_PRODUCTS_DIR E127589B46F33617D24F341DBAE1E90D fileRef 571096112AA1AD00C10596D216783599 isa PBXBuildFile settings COMPILER_FLAGS -DOS_OBJECT_USE_OBJC=0 E145A7E23B46A8C1E1549511735A041D fileRef 69C3D12EC1F2F8AE628C05AC390D9D61 isa PBXBuildFile settings ATTRIBUTES Public E287D744F886B15E1060AFEA475FC727 fileRef 4358F868C1553FF4273B0726E5AD2012 isa PBXBuildFile E2EBD18BA89D3FF648947DF31FA12D44 fileRef 24FAEADA1249F6042B4B4B135CDD0DF9 isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc E2FE87384A67337831E53490EEA8BF11 buildConfigurationList 0732716566238BF76BBF762F85C8ABAF buildPhases F90E42BE0540AE823AEBE381B7F2AF8C 886428740DB164747FBA1935F65900DF 53FA1A78374A52A66FBFD0691645BCB8 buildRules dependencies D583D511354F609B73EB28F4CFA2C32A 8EE5D8EDB6C595B190E81B76D3190EB3 C3D6913F5E4E594A29E40A526602BBB7 299B50B04F45E1A2F52075C3B7A1AEB2 isa PBXNativeTarget name MWPhotoBrowser productName MWPhotoBrowser productReference 50F87BFDC26A0B4CC6DE06D3ACBD0714 productType com.apple.product-type.library.static E348B132ABCC246013AE3A5532BC609F includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name FBSnapshotTestCase.h path FBSnapshotTestCase/FBSnapshotTestCase.h sourceTree <group> E40400D97F14785D20A7A5594F0E2C56 fileRef 1398D1B84667A17FCB66182F776EAC42 isa PBXBuildFile settings ATTRIBUTES Public E4436D58F27EAC932F3CC2A7619D5FD3 includeInIndex 1 isa PBXFileReference path ImageSelectedSmallOn@2x.png sourceTree <group> E4AD00C4B132C99837C22CC9031DE9F8 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPMatchers+beCloseTo.h path Expecta/Matchers/EXPMatchers+beCloseTo.h sourceTree <group> E54B859019C3D44B55F4723880F93D4D fileRef E7E794ADE2D5267DF8949DD0A42B935B isa PBXBuildFile settings ATTRIBUTES Public E5EA159155EA435901485FBB8111088C includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h path DACircularProgress-prefix.pch sourceTree <group> E6798A1D89E725C8EB02146EE4B0E31E includeInIndex 1 isa PBXFileReference path ImageSelectedOff@2x.png sourceTree <group> E7B8141DB24F5AA0D1F847AFEC0DB018 baseConfigurationReference 0557FD60A45F62CD51B4972ECE5C2584 buildSettings ENABLE_STRICT_OBJC_MSGSEND YES GCC_PREFIX_HEADER Target Support Files/MBProgressHUD/MBProgressHUD-prefix.pch IPHONEOS_DEPLOYMENT_TARGET 7.0 MTL_ENABLE_DEBUG_INFO YES OTHER_LDFLAGS OTHER_LIBTOOLFLAGS PRIVATE_HEADERS_FOLDER_PATH PRODUCT_NAME $(TARGET_NAME) PUBLIC_HEADERS_FOLDER_PATH SDKROOT iphoneos SKIP_INSTALL YES isa XCBuildConfiguration name Debug E7E794ADE2D5267DF8949DD0A42B935B includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h path UIImage+MWPhotoBrowser.h sourceTree <group> E81C08F5CCC0A5FBFF4559E2173164C7 buildActionMask 2147483647 files 70D801E0B95C23BCC270E4EC8659D463 2989492A1A182744E36FD61DF1746D94 isa PBXFrameworksBuildPhase runOnlyForDeploymentPostprocessing 0 E867CBF850D20C314BF4BD790432455D fileRef 05DA7841521B75C4D0C303F32E518117 isa PBXBuildFile settings ATTRIBUTES Public E8709320B1728545E4889D6232A39109 fileRef 216490FDC8376C36DB920710C65B92F3 isa PBXBuildFile E906DDCC926C2EA5161E326AB04AC53F includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name EXPBlockDefinedMatcher.m path Expecta/EXPBlockDefinedMatcher.m sourceTree <group> E97CDC7A1DFD18DAAD3C505929F32105 fileRef 9604D510A96A637DD9AE0D304B68A5A2 isa PBXBuildFile E99804B4B10C0A5C7C08E5EF962804CE fileRef 0A9B8E3E35177F52A643739AC50B00FA isa PBXBuildFile E9AAC3590B391F4CFAF5F8348E6A8FE4 buildConfigurationList F4A25246330C837CF7FB43FCC4E2A63F buildPhases 4C9C7890ED44DBC3090EEDFD92749F0A 1684C2FDDE674F14B731CC940021D8C6 3907B3A4464A3069E24142505C76338D buildRules dependencies isa PBXNativeTarget name Specta productName Specta productReference 7CF892960748F43245AD2B5CD2AC41A5 productType com.apple.product-type.library.static EB4DE2009B511D62CF6BFE67FA73E321 includeInIndex 1 isa PBXFileReference lastKnownFileType text path Pods-MWPhotoBrowser_Example-acknowledgements.markdown sourceTree <group> EB75F515760C184B2ED65E8649DC5792 fileRef 56228B3CBF246784930BFE7B29EFDD04 isa PBXBuildFile settings COMPILER_FLAGS -DOS_OBJECT_USE_OBJC=0 EB88EE844F8ADBE7E78B381D8832162D includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPMatchers+beFalsy.h path Expecta/Matchers/EXPMatchers+beFalsy.h sourceTree <group> EC0223776620A3EA741C855D0211CB0A includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name SDWebImagePrefetcher.h path SDWebImage/SDWebImagePrefetcher.h sourceTree <group> EC9E28E1F941952C6708902F41FD209D includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name EXPMatchers+beSupersetOf.m path Expecta/Matchers/EXPMatchers+beSupersetOf.m sourceTree <group> ECE81C5981477AA7D254ACC70534BE23 baseConfigurationReference 6F1FCA0BD48258CDEC1B14D704BA3E5D buildSettings ENABLE_STRICT_OBJC_MSGSEND YES IPHONEOS_DEPLOYMENT_TARGET 7.0 MACH_O_TYPE staticlib MTL_ENABLE_DEBUG_INFO YES OTHER_LDFLAGS OTHER_LIBTOOLFLAGS PODS_ROOT $(SRCROOT) PRODUCT_NAME $(TARGET_NAME) SDKROOT iphoneos SKIP_INSTALL YES isa XCBuildConfiguration name Debug ED177B9B7FCFE4E41DCDCCFB49692E1D fileRef 5289709799A254B807701A658BD9D425 isa PBXBuildFile ED1EC819236EDC3B337FCB044454FEB6 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPMatchers+beLessThan.h path Expecta/Matchers/EXPMatchers+beLessThan.h sourceTree <group> EDC1E77817E2D0252126B98EB0206A62 fileRef 46F871A74D831312A633CCFC0A94F75D isa PBXBuildFile EDDE097EFD40E9A4E32E4B44C97CE44B fileRef 10E446637EC5FF03088009E61B491E75 isa PBXBuildFile settings COMPILER_FLAGS -DOS_OBJECT_USE_OBJC=0 EE04EE8ADAB8F7DF7FCEFDDD1E801BE6 containerPortal D41D8CD98F00B204E9800998ECF8427E isa PBXContainerItemProxy proxyType 1 remoteGlobalIDString 7D9E32DAA93E15CFCF77A5BCD2D4BAA2 remoteInfo FBSnapshotTestCase EE52A320EC3155B114104E06396D1B59 fileRef 809101940909F56A7B70CE17D447640B isa PBXBuildFile settings ATTRIBUTES Public EF1C003613925663F17E023C73AF519C buildActionMask 2147483647 files 5EB4F27B76513AE918BA5AE0868E87C8 isa PBXHeadersBuildPhase runOnlyForDeploymentPostprocessing 0 EF4D256729962DC82677BF90E861294B includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h path MBProgressHUD.h sourceTree <group> EF5AC1222120F3BAB7481DA9615352B0 includeInIndex 1 isa PBXFileReference lastKnownFileType text.xcconfig path FBSnapshotTestCase.xcconfig sourceTree <group> EF75C10714B7CFF9071553F07D94A550 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name SPTTestSuite.h path Specta/Specta/SPTTestSuite.h sourceTree <group> EF9006327AFFDBED3FD589DA04E2D36A fileRef C655D4974F9982F0810618C9AC07216F isa PBXBuildFile settings COMPILER_FLAGS -DOS_OBJECT_USE_OBJC=0 EFE77B95F063C96D9B07BC2A988AB403 fileRef 9839B551DAF141B99431C2238C8EEBC0 isa PBXBuildFile F0A8CE7CF766D19972CF0934C1652293 buildActionMask 2147483647 files CA0DFA401B2A45337DFC72F893A060EF isa PBXSourcesBuildPhase runOnlyForDeploymentPostprocessing 0 F1948E226356BF652752F9FA7317BC73 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name SPTSpec.m path Specta/Specta/SPTSpec.m sourceTree <group> F1F4E65611F5567A86AF797EAC3E225B fileRef B0D25391E290ADE1BF90E18B8281F5F3 isa PBXBuildFile settings COMPILER_FLAGS -fno-objc-arc F2168F9F63056AC8BFB344608C4158BE fileRef 3CAA21F3728659DC1B58A336233AEB79 isa PBXBuildFile F2334A461A36CF285AC6F7E96F8FE0DB includeInIndex 1 isa PBXFileReference path ImageSelectedOn.png sourceTree <group> F26631BB63543446C157972F2F6D1475 fileRef F2334A461A36CF285AC6F7E96F8FE0DB isa PBXBuildFile F2A9AAC03867BB4990470BAC5912E030 buildActionMask 2147483647 files 53FB0CBA14BE53F79F4EB4E7B46C523B 072C184628072EDE522629576F1716DE isa PBXFrameworksBuildPhase runOnlyForDeploymentPostprocessing 0 F2BDF8E898C762EFB3844211CE201024 buildActionMask 2147483647 files isa PBXFrameworksBuildPhase runOnlyForDeploymentPostprocessing 0 F2F5671C4613023EE94A2E62DA06031D buildConfigurations E7B8141DB24F5AA0D1F847AFEC0DB018 C5AFFFCB54FD9036E7BC9BA54ADAAF30 defaultConfigurationIsVisible 0 defaultConfigurationName Release isa XCConfigurationList F314DFE6C76A8EEBC3D8CAF03EB89850 fileRef 75F83323F205A277BEC18AABDA0FA739 isa PBXBuildFile F3BB0A963277282C6BF73E81250B6943 fileRef 38777F32EB3F13ED8D2047C1C9ED6D6D isa PBXBuildFile settings COMPILER_FLAGS -DOS_OBJECT_USE_OBJC=0 F3FAB75D28A2755E4F9F79FB9F2AF5DB includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name SpectaDSL.m path Specta/Specta/SpectaDSL.m sourceTree <group> F40AD0DE1188AA331D98100B8766D6C8 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name EXPMatchers+beLessThan.m path Expecta/Matchers/EXPMatchers+beLessThan.m sourceTree <group> F4A25246330C837CF7FB43FCC4E2A63F buildConfigurations 1C1837DF96AE65F662773DF1956BFF09 04D4EDE1F05B96EE8BB9DCD368608620 defaultConfigurationIsVisible 0 defaultConfigurationName Release isa XCConfigurationList F4CA468B5A9F8FF2A4DB8B236A8E71BF fileRef EB88EE844F8ADBE7E78B381D8832162D isa PBXBuildFile settings ATTRIBUTES Public F4CDA5FA9197A41E0081E84F932906EB children D351C08D4390F427D8E1BEC3CB8A57BD isa PBXGroup name Frameworks sourceTree <group> F5551459CFCB6B7914E9EC1783277D94 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h path Expecta+Snapshots-prefix.pch sourceTree <group> F5963246D32249D4D198CE8A36B4DD12 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPMatchers+raiseWithReason.h path Expecta/Matchers/EXPMatchers+raiseWithReason.h sourceTree <group> F5F4D07451FB3E10EE7C16A83010425F fileRef DF11024BF511A0D2225D4BA84770CED7 isa PBXBuildFile F601833D09980E16136321674463BE81 isa PBXTargetDependency name Expecta target 2F501FE84845EAD97B9087DAFCBBEE0E targetProxy C9B5F86F6244D78D3F80384F609427C6 F6045A7604731086F9E1A9412E290302 baseConfigurationReference FAF77DDAA41AC524C38F1D92F2EE01AB buildSettings ENABLE_STRICT_OBJC_MSGSEND YES GCC_PREFIX_HEADER Target Support Files/Expecta+Snapshots/Expecta+Snapshots-prefix.pch IPHONEOS_DEPLOYMENT_TARGET 7.0 MTL_ENABLE_DEBUG_INFO YES OTHER_LDFLAGS OTHER_LIBTOOLFLAGS PRIVATE_HEADERS_FOLDER_PATH PRODUCT_NAME $(TARGET_NAME) PUBLIC_HEADERS_FOLDER_PATH SDKROOT iphoneos SKIP_INSTALL YES isa XCBuildConfiguration name Debug F629BC640170D45C22A8048A42BAB040 children 37E4F69B3E6AFDEE8D68A3430CEC8BF5 40D39B6435EEF47A85F4E3FB684E72FE 60ECF290B248258F453088B6258302E6 C655D4974F9982F0810618C9AC07216F DDAA00E6C2E81A6C35534DD632143F09 isa PBXGroup name DACircularProgress path DACircularProgress sourceTree <group> F6E655732BA75BED75FFACA472080CC4 children 0A924BC847BA99EBCBD0588603448B05 E906DDCC926C2EA5161E326AB04AC53F 7AAD7709C9FF6828905D6A28E69C4221 DFA142DA2A1E6AB454178652BAC9A075 D70BED84191514A998A08D7F3B8BBB17 BA9D20AF1E1E4CF225AA2D772049336D 1D536F869C3B7D603AC4630F3F6AB919 3DEC242716623D70F4CC9F4A1D32805C D09A95F4452843967F4C8F3FA5EC1A68 4BE4F23029A1EDB234CF7F2AEB043EB0 3E7FFEF7BFB0350CE8FCC2D6D64D1396 92F064516748B6AA4C6E303A38C3FE6A 8136A5BFC35DA48ABC2C0D94EA79B7F0 15082EF0D1C2F7B77B63E0116BDA622C 700F6F4AFED22AEAA1D8943236F94924 79996E262256F57DB50CD16E5EBE5D52 232510EC13760470E86AABE302EF2B8E 6ED6BE9A61002A3AF6D4C834E2277D8C E4AD00C4B132C99837C22CC9031DE9F8 D68BA9A93FD415E751DFA95CCF7DCB8D EB88EE844F8ADBE7E78B381D8832162D B0D25391E290ADE1BF90E18B8281F5F3 DA7F16221783A03C084D9BAAF7F404DB 1BB15402D8C2FB01A198273BA1991DED 0683DF2C9B7FC27B576AB4B7B3B146ED 7DACEAF8582049D6EB8B4CFFB6CA48CD B5969E17FAFFDC548233DC602AAC6211 AE917511985F6891AF3F8233B9D34001 2E7CBF297AA8A0E93C23BFAF1BE94DEE 1AB26E75F7BCD415D6AD6B8BA5F03DDB 3C5A51B8AE7322A210CF01AFBA648598 44D0D04C0DF98EC05B4909F397D6F7FF 862018A7CC10880108A2BDC5146C8F3F F9B2375A6E20F2774E7B3F08FB92909E 29E6F040DB8ACF8886F7FE628840C480 3173089CCB6C063D260C58DD7BA3ABF5 ED1EC819236EDC3B337FCB044454FEB6 F40AD0DE1188AA331D98100B8766D6C8 809101940909F56A7B70CE17D447640B 746A80915CF52BE732D200BAA851FBAB 2779B434631901F1434BC316E3FB5AE9 40DAD22BCDD38D058B1C9A312C42DF49 71865C216F48AC822D486C8D9B03B13C 0AB1B49E98BEDDFC8A45BB3C1FB54076 9A9B45D988FA49FD87F8587C42403EC5 EC9E28E1F941952C6708902F41FD209D 10E68FD4A81F628778B6A4992DB9D952 C2CEB9C1175C4FC4D5BE5C6EF285E0A4 FDE2A8151D1FDB6749FB32276220BE7C 63010FB3922AB3E2DDD6D8CDD2D10BFA 981F3EB0C06E97770D2421E673B013A1 9D5AE460F3B78B02A8AE8E5AD0DCC013 567A47FABAD9EE8B7DF32295CC53F937 56BD615DEEA3BC153C0B73BAC8A60A13 D10BA2216A9B5F41557B4FC448EE865C 70922EF029715CA28E814F87373F29C4 AB8960E6933BD274C11AD9C15CF410AF 6CF3D336192327338965DC88856BDFCF 79D73FB4B6A3C92513B81243EA72E78C 7250BEF99A7686EAB50B17986FDCED3E 5F878B29A406784703BC9F07FBFDD99F 3DA2CF997A09E53F375CF0DEEF25AFCF B2CBEFFA54424B8FBBE1EB23EC879E69 788D548DE837CA2FA9D2D744D64FA847 F5963246D32249D4D198CE8A36B4DD12 FFC94BC21DE6B02A85C4BA9B703D222D 05DA7841521B75C4D0C303F32E518117 24FAEADA1249F6042B4B4B135CDD0DF9 865FAF448207E691D600F40C4ACF7C58 B23D9E49B5B3A5C76EAFFC9C7F40B4B9 66D391F04A8D672C0D42510DE811E7B2 18391089432560093C31CCA48ABB46AE 5A06D9F124A077FE3345C713135D7E6C 4209F7E4332FDC16B4C407CA07C3E8E8 isa PBXGroup name Expecta path Expecta sourceTree <group> F84C57B89A97BB8108482354FFD8E157 baseConfigurationReference 86A1ADA0F8A843FA09BFF9142BEF76D5 buildSettings ENABLE_STRICT_OBJC_MSGSEND YES GCC_PREFIX_HEADER Target Support Files/Expecta/Expecta-prefix.pch IPHONEOS_DEPLOYMENT_TARGET 7.0 MTL_ENABLE_DEBUG_INFO YES OTHER_LDFLAGS OTHER_LIBTOOLFLAGS PRIVATE_HEADERS_FOLDER_PATH PRODUCT_NAME $(TARGET_NAME) PUBLIC_HEADERS_FOLDER_PATH SDKROOT iphoneos SKIP_INSTALL YES isa XCBuildConfiguration name Debug F88B870D7D0531FB54EAD23EB162DB70 fileRef 8DC04A9EEA14F0D2300FB6C0CAEB9CAB isa PBXBuildFile F90E42BE0540AE823AEBE381B7F2AF8C buildActionMask 2147483647 files 453678659359A72C87495DDD6D9BD0C6 C5BD0A3729BF33E203724A88CFE727BC A0BA05DFEF00406DC4E8898D36D7418D CF37F67F6269058934EB779D902EE7AB 1A0D2B172232171C3EB6DC4DFE575EE7 6EEA87D8E87914D02AA319693371EE2C E8709320B1728545E4889D6232A39109 5023A97277B66BB3E83DA779FEBAE80C D7A0524D2A055F825B4DF69A9DBBFF98 ED177B9B7FCFE4E41DCDCCFB49692E1D isa PBXSourcesBuildPhase runOnlyForDeploymentPostprocessing 0 F9B0A541E338684F945B7539D802CF64 fileRef 1A1E8E8494B9BFCEBBD8273148D44386 isa PBXBuildFile settings ATTRIBUTES Public F9B2375A6E20F2774E7B3F08FB92909E includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name EXPMatchers+beInTheRangeOf.m path Expecta/Matchers/EXPMatchers+beInTheRangeOf.m sourceTree <group> FAC85E18FE868D87FFB2E13C182F4DB9 fileRef C9BF70FB69B8740763B185C23C560434 isa PBXBuildFile FAF77DDAA41AC524C38F1D92F2EE01AB includeInIndex 1 isa PBXFileReference lastKnownFileType text.xcconfig path Expecta+Snapshots.xcconfig sourceTree <group> FB42B2F11B39412F3015A082008F1782 fileRef AE6C39B87AE391EC710DA8FB520ED3F5 isa PBXBuildFile settings ATTRIBUTES Public FBADF2C12D471003AEB710314DA3FF49 fileRef 28A20AE3D7A1F50CAD0D9D8B71205EC6 isa PBXBuildFile FC1172043A5F0542D166D63AE9EBC260 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc path MBProgressHUD-dummy.m sourceTree <group> FC1FB976E1C29A75089D2374BDFCCC44 fileRef C36BB9623ECA2B27942B66986A784EC9 isa PBXBuildFile FC214077677D4F02E03C6C85F774E028 fileRef FC1172043A5F0542D166D63AE9EBC260 isa PBXBuildFile FCA01B035C5FC9AF0969977B2C54A7A9 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc path MWGridCell.m sourceTree <group> FD6FA76757975A1FC954269CE5739798 baseConfigurationReference EF5AC1222120F3BAB7481DA9615352B0 buildSettings ENABLE_STRICT_OBJC_MSGSEND YES GCC_PREFIX_HEADER Target Support Files/FBSnapshotTestCase/FBSnapshotTestCase-prefix.pch IPHONEOS_DEPLOYMENT_TARGET 7.0 MTL_ENABLE_DEBUG_INFO YES OTHER_LDFLAGS OTHER_LIBTOOLFLAGS PRIVATE_HEADERS_FOLDER_PATH PRODUCT_NAME $(TARGET_NAME) PUBLIC_HEADERS_FOLDER_PATH SDKROOT iphoneos SKIP_INSTALL YES isa XCBuildConfiguration name Debug FDE2A8151D1FDB6749FB32276220BE7C includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.h name EXPMatchers+conformTo.h path Expecta/Matchers/EXPMatchers+conformTo.h sourceTree <group> FDEE1AFEA9C68977819B7D652EE1F6F2 children 362D9038C23D156F9F055EDF5565A5F1 isa PBXGroup name Pod path Pod sourceTree <group> FDF72740DBC37AFACFED73ED42282383 fileRef A6A21F67B2A713138BF1A404E3460084 isa PBXBuildFile FE2F1D7B9D9FCEA148517E4657B243F4 buildConfigurationList F2F5671C4613023EE94A2E62DA06031D buildPhases 098439AB598DDF1AF51146A2E3E2E562 E81C08F5CCC0A5FBFF4559E2173164C7 EF1C003613925663F17E023C73AF519C buildRules dependencies isa PBXNativeTarget name MBProgressHUD productName MBProgressHUD productReference D7F1441E30E059AC17B4C1CE1464C07B productType com.apple.product-type.library.static FE4A445C1692178764D9C71EAC64ABAC containerPortal D41D8CD98F00B204E9800998ECF8427E isa PBXContainerItemProxy proxyType 1 remoteGlobalIDString 05257CC76400D71826E603561A0F73E6 remoteInfo Expecta+Snapshots FE9D7A964178EF6E3CE5FC52134DF825 includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name UIImage+MultiFormat.m path SDWebImage/UIImage+MultiFormat.m sourceTree <group> FEFA85FDDB15B89CF28DF12C1C2071E1 fileRef 1EF011C5AA1726984A1B50A46191CE9B isa PBXBuildFile settings ATTRIBUTES Public FF6817E7D2D11A6D747BFAEA934D0FD1 containerPortal D41D8CD98F00B204E9800998ECF8427E isa PBXContainerItemProxy proxyType 1 remoteGlobalIDString FE2F1D7B9D9FCEA148517E4657B243F4 remoteInfo MBProgressHUD FFC94BC21DE6B02A85C4BA9B703D222D includeInIndex 1 isa PBXFileReference lastKnownFileType sourcecode.c.objc name EXPMatchers+raiseWithReason.m path Expecta/Matchers/EXPMatchers+raiseWithReason.m sourceTree <group> FFF549D1FBC0937A99BED40CC44BC155 fileRef C266061F84E4CFB93AF99D21EEB13401 isa PBXBuildFile settings ATTRIBUTES Public rootObject D41D8CD98F00B204E9800998ECF8427E ================================================ FILE: Example/Pods/Pods.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/MWPhotoBrowser.xcscheme ================================================ ================================================ FILE: Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/Pods-MWPhotoBrowser_Example-MWPhotoBrowser-MWPhotoBrowser.xcscheme ================================================ ================================================ FILE: Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/Pods-MWPhotoBrowser_Example-MWPhotoBrowser.xcscheme ================================================ ================================================ FILE: Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/Pods-MWPhotoBrowser_Tests-MWPhotoBrowser-MWPhotoBrowser.xcscheme ================================================ ================================================ FILE: Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/Pods-MWPhotoBrowser_Tests-MWPhotoBrowser.xcscheme ================================================ ================================================ FILE: Example/Pods/SDWebImage/LICENSE ================================================ Copyright (c) 2009 Olivier Poitrey Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Example/Pods/SDWebImage/README.md ================================================ Web Image ========= [![Build Status](http://img.shields.io/travis/rs/SDWebImage/master.svg?style=flat)](https://travis-ci.org/rs/SDWebImage) [![Pod Version](http://img.shields.io/cocoapods/v/SDWebImage.svg?style=flat)](http://cocoadocs.org/docsets/SDWebImage/) [![Pod Platform](http://img.shields.io/cocoapods/p/SDWebImage.svg?style=flat)](http://cocoadocs.org/docsets/SDWebImage/) [![Pod License](http://img.shields.io/cocoapods/l/SDWebImage.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0.html) [![Dependency Status](https://www.versioneye.com/objective-c/sdwebimage/3.3/badge.svg?style=flat)](https://www.versioneye.com/objective-c/sdwebimage/3.3) [![Reference Status](https://www.versioneye.com/objective-c/sdwebimage/reference_badge.svg?style=flat)](https://www.versioneye.com/objective-c/sdwebimage/references) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/rs/SDWebImage) This library provides a category for UIImageView with support for remote images coming from the web. It provides: - An `UIImageView` category adding web image and cache management to the Cocoa Touch framework - An asynchronous image downloader - An asynchronous memory + disk image caching with automatic cache expiration handling - Animated GIF support - WebP format support - A background image decompression - A guarantee that the same URL won't be downloaded several times - A guarantee that bogus URLs won't be retried again and again - A guarantee that main thread will never be blocked - Performances! - Use GCD and ARC - Arm64 support NOTE: The version 3.0 of SDWebImage isn't fully backward compatible with 2.0 and requires iOS 5.1.1 minimum deployment version. If you need iOS < 5.0 support, please use the last [2.0 version](https://github.com/rs/SDWebImage/tree/2.0-compat). [How is SDWebImage better than X?](https://github.com/rs/SDWebImage/wiki/How-is-SDWebImage-better-than-X%3F) Who Use It ---------- Find out [who uses SDWebImage](https://github.com/rs/SDWebImage/wiki/Who-Uses-SDWebImage) and add your app to the list. How To Use ---------- API documentation is available at [CocoaDocs - SDWebImage](http://cocoadocs.org/docsets/SDWebImage/) ### Using UIImageView+WebCache category with UITableView Just #import the UIImageView+WebCache.h header, and call the sd_setImageWithURL:placeholderImage: method from the tableView:cellForRowAtIndexPath: UITableViewDataSource method. Everything will be handled for you, from async downloads to caching management. ```objective-c #import ... - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *MyIdentifier = @"MyIdentifier"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier] autorelease]; } // Here we use the new provided sd_setImageWithURL: method to load the web image [cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder.png"]]; cell.textLabel.text = @"My Text"; return cell; } ``` ### Using blocks With blocks, you can be notified about the image download progress and whenever the image retrieval has completed with success or not: ```objective-c // Here we use the new provided sd_setImageWithURL: method to load the web image [cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder.png"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { ... completion code here ... }]; ``` Note: neither your success nor failure block will be call if your image request is canceled before completion. ### Using SDWebImageManager The SDWebImageManager is the class behind the UIImageView+WebCache category. It ties the asynchronous downloader with the image cache store. You can use this class directly to benefit from web image downloading with caching in another context than a UIView (ie: with Cocoa). Here is a simple example of how to use SDWebImageManager: ```objective-c SDWebImageManager *manager = [SDWebImageManager sharedManager]; [manager downloadImageWithURL:imageURL options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) { // progression tracking code } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { if (image) { // do something with image } }]; ``` ### Using Asynchronous Image Downloader Independently It's also possible to use the async image downloader independently: ```objective-c SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader]; [downloader downloadImageWithURL:imageURL options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) { // progression tracking code } completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) { if (image && finished) { // do something with image } }]; ``` ### Using Asynchronous Image Caching Independently It is also possible to use the async based image cache store independently. SDImageCache maintains a memory cache and an optional disk cache. Disk cache write operations are performed asynchronous so it doesn't add unnecessary latency to the UI. The SDImageCache class provides a singleton instance for convenience but you can create your own instance if you want to create separated cache namespace. To lookup the cache, you use the `queryDiskCacheForKey:done:` method. If the method returns nil, it means the cache doesn't currently own the image. You are thus responsible for generating and caching it. The cache key is an application unique identifier for the image to cache. It is generally the absolute URL of the image. ```objective-c SDImageCache *imageCache = [[SDImageCache alloc] initWithNamespace:@"myNamespace"]; [imageCache queryDiskCacheForKey:myCacheKey done:^(UIImage *image) { // image is not nil if image was found }]; ``` By default SDImageCache will lookup the disk cache if an image can't be found in the memory cache. You can prevent this from happening by calling the alternative method `imageFromMemoryCacheForKey:`. To store an image into the cache, you use the storeImage:forKey: method: ```objective-c [[SDImageCache sharedImageCache] storeImage:myImage forKey:myCacheKey]; ``` By default, the image will be stored in memory cache as well as on disk cache (asynchronously). If you want only the memory cache, use the alternative method storeImage:forKey:toDisk: with a negative third argument. ### Using cache key filter Sometime, you may not want to use the image URL as cache key because part of the URL is dynamic (i.e.: for access control purpose). SDWebImageManager provides a way to set a cache key filter that takes the NSURL as input, and output a cache key NSString. The following example sets a filter in the application delegate that will remove any query-string from the URL before to use it as a cache key: ```objective-c - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { SDWebImageManager.sharedManager.cacheKeyFilter = ^(NSURL *url) { url = [[NSURL alloc] initWithScheme:url.scheme host:url.host path:url.path]; return [url absoluteString]; }; // Your app init code... return YES; } ``` Common Problems --------------- ### Using dynamic image size with UITableViewCell UITableView determines the size of the image by the first image set for a cell. If your remote images don't have the same size as your placeholder image, you may experience strange anamorphic scaling issue. The following article gives a way to workaround this issue: [http://www.wrichards.com/blog/2011/11/sdwebimage-fixed-width-cell-images/](http://www.wrichards.com/blog/2011/11/sdwebimage-fixed-width-cell-images/) ### Handle image refresh SDWebImage does very aggressive caching by default. It ignores all kind of caching control header returned by the HTTP server and cache the returned images with no time restriction. It implies your images URLs are static URLs pointing to images that never change. If the pointed image happen to change, some parts of the URL should change accordingly. If you don't control the image server you're using, you may not be able to change the URL when its content is updated. This is the case for Facebook avatar URLs for instance. In such case, you may use the `SDWebImageRefreshCached` flag. This will slightly degrade the performance but will respect the HTTP caching control headers: ``` objective-c [imageView sd_setImageWithURL:[NSURL URLWithString:@"https://graph.facebook.com/olivier.poitrey/picture"] placeholderImage:[UIImage imageNamed:@"avatar-placeholder.png"] options:SDWebImageRefreshCached]; ``` ### Add a progress indicator See this category: https://github.com/JJSaccolo/UIActivityIndicator-for-SDWebImage Installation ------------ There are three ways to use SDWebImage in your project: - using Cocoapods - copying all the files into your project - importing the project as a static library ### Installation with CocoaPods [CocoaPods](http://cocoapods.org/) is a dependency manager for Objective-C, which automates and simplifies the process of using 3rd-party libraries in your projects. See the [Get Started](http://cocoapods.org/#get_started) section for more details. #### Podfile ``` platform :ios, '6.1' pod 'SDWebImage', '~>3.7' ``` If you are using Swift, be sure to add `use_frameworks!` and set your target to iOS 8+: ``` platform :ios, '8.0' use_frameworks! ``` #### Subspecs There are 3 subspecs available now: `Core`, `MapKit` and `WebP` (this means you can install only some of the SDWebImage modules. By default, you get just `Core`, so if you need `WebP`, you need to specify it). Podfile example: ``` pod 'SDWebImage/WebP' ``` ### Installation with Carthage (iOS 8+) [Carthage](https://github.com/Carthage/Carthage) is a lightweight dependency manager for Swift and Objective-C. It leverages CocoaTouch modules and is less invasive than CocoaPods. To install with carthage, follow the instruction on [Carthage](https://github.com/Carthage/Carthage) #### Cartfile ``` github "rs/SDWebImage" ``` #### Usage Swift If you installed using CocoaPods: ``` import SDWebImage ``` If you installed manually: ``` import WebImage ``` Objective-C ``` @import WebImage; ``` ### Installation by cloning the repository In order to gain access to all the files from the repository, you should clone it. ``` git clone --recursive https://github.com/rs/SDWebImage.git ``` ### Add the SDWebImage project to your project - Download and unzip the last version of the framework from the [download page](https://github.com/rs/SDWebImage/releases) - Right-click on the project navigator and select "Add Files to "Your Project": - In the dialog, select SDWebImage.framework: - Check the "Copy items into destination group's folder (if needed)" checkbox ### Add dependencies - In you application project app’s target settings, find the "Build Phases" section and open the "Link Binary With Libraries" block: - Click the "+" button again and select the "ImageIO.framework", this is needed by the progressive download feature: ### Add Linker Flag Open the "Build Settings" tab, in the "Linking" section, locate the "Other Linker Flags" setting and add the "-ObjC" flag: ![Other Linker Flags](http://dl.dropbox.com/u/123346/SDWebImage/10_other_linker_flags.jpg) Alternatively, if this causes compilation problems with frameworks that extend optional libraries, such as Parse, RestKit or opencv2, instead of the -ObjC flag use: ``` -force_load SDWebImage.framework/Versions/Current/SDWebImage ``` If you're using Cocoa Pods and have any frameworks that extend optional libraries, such as Parsen RestKit or opencv2, instead of the -ObjC flag use: ``` -force_load $(TARGET_BUILD_DIR)/libPods.a ``` and this: ``` $(inherited) ``` ### Import headers in your source files In the source files where you need to use the library, import the header file: ```objective-c #import ``` ### Build Project At this point your workspace should build without error. If you are having problem, post to the Issue and the community can help you solve it. Future Enhancements ------------------- - LRU memory cache cleanup instead of reset on memory warning ## Licenses All source code is licensed under the [MIT License](https://raw.github.com/rs/SDWebImage/master/LICENSE). ================================================ FILE: Example/Pods/SDWebImage/SDWebImage/NSData+ImageContentType.h ================================================ // // Created by Fabrice Aneche on 06/01/14. // Copyright (c) 2014 Dailymotion. All rights reserved. // #import @interface NSData (ImageContentType) /** * Compute the content type for an image data * * @param data the input data * * @return the content type as string (i.e. image/jpeg, image/gif) */ + (NSString *)sd_contentTypeForImageData:(NSData *)data; @end @interface NSData (ImageContentTypeDeprecated) + (NSString *)contentTypeForImageData:(NSData *)data __deprecated_msg("Use `sd_contentTypeForImageData:`"); @end ================================================ FILE: Example/Pods/SDWebImage/SDWebImage/NSData+ImageContentType.m ================================================ // // Created by Fabrice Aneche on 06/01/14. // Copyright (c) 2014 Dailymotion. All rights reserved. // #import "NSData+ImageContentType.h" @implementation NSData (ImageContentType) + (NSString *)sd_contentTypeForImageData:(NSData *)data { uint8_t c; [data getBytes:&c length:1]; switch (c) { case 0xFF: return @"image/jpeg"; case 0x89: return @"image/png"; case 0x47: return @"image/gif"; case 0x49: case 0x4D: return @"image/tiff"; case 0x52: // R as RIFF for WEBP if ([data length] < 12) { return nil; } NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding]; if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) { return @"image/webp"; } return nil; } return nil; } @end @implementation NSData (ImageContentTypeDeprecated) + (NSString *)contentTypeForImageData:(NSData *)data { return [self sd_contentTypeForImageData:data]; } @end ================================================ FILE: Example/Pods/SDWebImage/SDWebImage/SDImageCache.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDWebImageCompat.h" typedef NS_ENUM(NSInteger, SDImageCacheType) { /** * The image wasn't available the SDWebImage caches, but was downloaded from the web. */ SDImageCacheTypeNone, /** * The image was obtained from the disk cache. */ SDImageCacheTypeDisk, /** * The image was obtained from the memory cache. */ SDImageCacheTypeMemory }; typedef void(^SDWebImageQueryCompletedBlock)(UIImage *image, SDImageCacheType cacheType); typedef void(^SDWebImageCheckCacheCompletionBlock)(BOOL isInCache); typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger totalSize); /** * SDImageCache maintains a memory cache and an optional disk cache. Disk cache write operations are performed * asynchronous so it doesn’t add unnecessary latency to the UI. */ @interface SDImageCache : NSObject /** * Decompressing images that are downloaded and cached can improve performance but can consume lot of memory. * Defaults to YES. Set this to NO if you are experiencing a crash due to excessive memory consumption. */ @property (assign, nonatomic) BOOL shouldDecompressImages; /** * disable iCloud backup [defaults to YES] */ @property (assign, nonatomic) BOOL shouldDisableiCloud; /** * use memory cache [defaults to YES] */ @property (assign, nonatomic) BOOL shouldCacheImagesInMemory; /** * The maximum "total cost" of the in-memory image cache. The cost function is the number of pixels held in memory. */ @property (assign, nonatomic) NSUInteger maxMemoryCost; /** * The maximum number of objects the cache should hold. */ @property (assign, nonatomic) NSUInteger maxMemoryCountLimit; /** * The maximum length of time to keep an image in the cache, in seconds */ @property (assign, nonatomic) NSInteger maxCacheAge; /** * The maximum size of the cache, in bytes. */ @property (assign, nonatomic) NSUInteger maxCacheSize; /** * Returns global shared cache instance * * @return SDImageCache global instance */ + (SDImageCache *)sharedImageCache; /** * Init a new cache store with a specific namespace * * @param ns The namespace to use for this cache store */ - (id)initWithNamespace:(NSString *)ns; /** * Init a new cache store with a specific namespace and directory * * @param ns The namespace to use for this cache store * @param directory Directory to cache disk images in */ - (id)initWithNamespace:(NSString *)ns diskCacheDirectory:(NSString *)directory; -(NSString *)makeDiskCachePath:(NSString*)fullNamespace; /** * Add a read-only cache path to search for images pre-cached by SDImageCache * Useful if you want to bundle pre-loaded images with your app * * @param path The path to use for this read-only cache path */ - (void)addReadOnlyCachePath:(NSString *)path; /** * Store an image into memory and disk cache at the given key. * * @param image The image to store * @param key The unique image cache key, usually it's image absolute URL */ - (void)storeImage:(UIImage *)image forKey:(NSString *)key; /** * Store an image into memory and optionally disk cache at the given key. * * @param image The image to store * @param key The unique image cache key, usually it's image absolute URL * @param toDisk Store the image to disk cache if YES */ - (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk; /** * Store an image into memory and optionally disk cache at the given key. * * @param image The image to store * @param recalculate BOOL indicates if imageData can be used or a new data should be constructed from the UIImage * @param imageData The image data as returned by the server, this representation will be used for disk storage * instead of converting the given image object into a storable/compressed image format in order * to save quality and CPU * @param key The unique image cache key, usually it's image absolute URL * @param toDisk Store the image to disk cache if YES */ - (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk; /** * Query the disk cache asynchronously. * * @param key The unique key used to store the wanted image */ - (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock; /** * Query the memory cache synchronously. * * @param key The unique key used to store the wanted image */ - (UIImage *)imageFromMemoryCacheForKey:(NSString *)key; /** * Query the disk cache synchronously after checking the memory cache. * * @param key The unique key used to store the wanted image */ - (UIImage *)imageFromDiskCacheForKey:(NSString *)key; /** * Remove the image from memory and disk cache synchronously * * @param key The unique image cache key */ - (void)removeImageForKey:(NSString *)key; /** * Remove the image from memory and disk cache asynchronously * * @param key The unique image cache key * @param completion An block that should be executed after the image has been removed (optional) */ - (void)removeImageForKey:(NSString *)key withCompletion:(SDWebImageNoParamsBlock)completion; /** * Remove the image from memory and optionally disk cache asynchronously * * @param key The unique image cache key * @param fromDisk Also remove cache entry from disk if YES */ - (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk; /** * Remove the image from memory and optionally disk cache asynchronously * * @param key The unique image cache key * @param fromDisk Also remove cache entry from disk if YES * @param completion An block that should be executed after the image has been removed (optional) */ - (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(SDWebImageNoParamsBlock)completion; /** * Clear all memory cached images */ - (void)clearMemory; /** * Clear all disk cached images. Non-blocking method - returns immediately. * @param completion An block that should be executed after cache expiration completes (optional) */ - (void)clearDiskOnCompletion:(SDWebImageNoParamsBlock)completion; /** * Clear all disk cached images * @see clearDiskOnCompletion: */ - (void)clearDisk; /** * Remove all expired cached image from disk. Non-blocking method - returns immediately. * @param completionBlock An block that should be executed after cache expiration completes (optional) */ - (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock; /** * Remove all expired cached image from disk * @see cleanDiskWithCompletionBlock: */ - (void)cleanDisk; /** * Get the size used by the disk cache */ - (NSUInteger)getSize; /** * Get the number of images in the disk cache */ - (NSUInteger)getDiskCount; /** * Asynchronously calculate the disk cache's size. */ - (void)calculateSizeWithCompletionBlock:(SDWebImageCalculateSizeBlock)completionBlock; /** * Async check if image exists in disk cache already (does not load the image) * * @param key the key describing the url * @param completionBlock the block to be executed when the check is done. * @note the completion block will be always executed on the main queue */ - (void)diskImageExistsWithKey:(NSString *)key completion:(SDWebImageCheckCacheCompletionBlock)completionBlock; /** * Check if image exists in disk cache already (does not load the image) * * @param key the key describing the url * * @return YES if an image exists for the given key */ - (BOOL)diskImageExistsWithKey:(NSString *)key; /** * Get the cache path for a certain key (needs the cache path root folder) * * @param key the key (can be obtained from url using cacheKeyForURL) * @param path the cache path root folder * * @return the cache path */ - (NSString *)cachePathForKey:(NSString *)key inPath:(NSString *)path; /** * Get the default cache path for a certain key * * @param key the key (can be obtained from url using cacheKeyForURL) * * @return the default cache path */ - (NSString *)defaultCachePathForKey:(NSString *)key; @end ================================================ FILE: Example/Pods/SDWebImage/SDWebImage/SDImageCache.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDImageCache.h" #import "SDWebImageDecoder.h" #import "UIImage+MultiFormat.h" #import // See https://github.com/rs/SDWebImage/pull/1141 for discussion @interface AutoPurgeCache : NSCache @end @implementation AutoPurgeCache - (id)init { self = [super init]; if (self) { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; } return self; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; } @end static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week // PNG signature bytes and data (below) static unsigned char kPNGSignatureBytes[8] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}; static NSData *kPNGSignatureData = nil; BOOL ImageDataHasPNGPreffix(NSData *data); BOOL ImageDataHasPNGPreffix(NSData *data) { NSUInteger pngSignatureLength = [kPNGSignatureData length]; if ([data length] >= pngSignatureLength) { if ([[data subdataWithRange:NSMakeRange(0, pngSignatureLength)] isEqualToData:kPNGSignatureData]) { return YES; } } return NO; } FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) { return image.size.height * image.size.width * image.scale * image.scale; } @interface SDImageCache () @property (strong, nonatomic) NSCache *memCache; @property (strong, nonatomic) NSString *diskCachePath; @property (strong, nonatomic) NSMutableArray *customPaths; @property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t ioQueue; @end @implementation SDImageCache { NSFileManager *_fileManager; } + (SDImageCache *)sharedImageCache { static dispatch_once_t once; static id instance; dispatch_once(&once, ^{ instance = [self new]; }); return instance; } - (id)init { return [self initWithNamespace:@"default"]; } - (id)initWithNamespace:(NSString *)ns { NSString *path = [self makeDiskCachePath:ns]; return [self initWithNamespace:ns diskCacheDirectory:path]; } - (id)initWithNamespace:(NSString *)ns diskCacheDirectory:(NSString *)directory { if ((self = [super init])) { NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns]; // initialise PNG signature data kPNGSignatureData = [NSData dataWithBytes:kPNGSignatureBytes length:8]; // Create IO serial queue _ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL); // Init default values _maxCacheAge = kDefaultCacheMaxCacheAge; // Init the memory cache _memCache = [[AutoPurgeCache alloc] init]; _memCache.name = fullNamespace; // Init the disk cache if (directory != nil) { _diskCachePath = [directory stringByAppendingPathComponent:fullNamespace]; } else { NSString *path = [self makeDiskCachePath:ns]; _diskCachePath = path; } // Set decompression to YES _shouldDecompressImages = YES; // memory cache enabled _shouldCacheImagesInMemory = YES; // Disable iCloud _shouldDisableiCloud = YES; dispatch_sync(_ioQueue, ^{ _fileManager = [NSFileManager new]; }); #if TARGET_OS_IPHONE // Subscribe to app events [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(clearMemory) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cleanDisk) name:UIApplicationWillTerminateNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backgroundCleanDisk) name:UIApplicationDidEnterBackgroundNotification object:nil]; #endif } return self; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; SDDispatchQueueRelease(_ioQueue); } - (void)addReadOnlyCachePath:(NSString *)path { if (!self.customPaths) { self.customPaths = [NSMutableArray new]; } if (![self.customPaths containsObject:path]) { [self.customPaths addObject:path]; } } - (NSString *)cachePathForKey:(NSString *)key inPath:(NSString *)path { NSString *filename = [self cachedFileNameForKey:key]; return [path stringByAppendingPathComponent:filename]; } - (NSString *)defaultCachePathForKey:(NSString *)key { return [self cachePathForKey:key inPath:self.diskCachePath]; } #pragma mark SDImageCache (private) - (NSString *)cachedFileNameForKey:(NSString *)key { const char *str = [key UTF8String]; if (str == NULL) { str = ""; } unsigned char r[CC_MD5_DIGEST_LENGTH]; CC_MD5(str, (CC_LONG)strlen(str), r); NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@", r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10], r[11], r[12], r[13], r[14], r[15], [[key pathExtension] isEqualToString:@""] ? @"" : [NSString stringWithFormat:@".%@", [key pathExtension]]]; return filename; } #pragma mark ImageCache // Init the disk cache -(NSString *)makeDiskCachePath:(NSString*)fullNamespace{ NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); return [paths[0] stringByAppendingPathComponent:fullNamespace]; } - (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk { if (!image || !key) { return; } // if memory cache is enabled if (self.shouldCacheImagesInMemory) { NSUInteger cost = SDCacheCostForImage(image); [self.memCache setObject:image forKey:key cost:cost]; } if (toDisk) { dispatch_async(self.ioQueue, ^{ NSData *data = imageData; if (image && (recalculate || !data)) { #if TARGET_OS_IPHONE // We need to determine if the image is a PNG or a JPEG // PNGs are easier to detect because they have a unique signature (http://www.w3.org/TR/PNG-Structure.html) // The first eight bytes of a PNG file always contain the following (decimal) values: // 137 80 78 71 13 10 26 10 // If the imageData is nil (i.e. if trying to save a UIImage directly or the image was transformed on download) // and the image has an alpha channel, we will consider it PNG to avoid losing the transparency int alphaInfo = CGImageGetAlphaInfo(image.CGImage); BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone || alphaInfo == kCGImageAlphaNoneSkipFirst || alphaInfo == kCGImageAlphaNoneSkipLast); BOOL imageIsPng = hasAlpha; // But if we have an image data, we will look at the preffix if ([imageData length] >= [kPNGSignatureData length]) { imageIsPng = ImageDataHasPNGPreffix(imageData); } if (imageIsPng) { data = UIImagePNGRepresentation(image); } else { data = UIImageJPEGRepresentation(image, (CGFloat)1.0); } #else data = [NSBitmapImageRep representationOfImageRepsInArray:image.representations usingType: NSJPEGFileType properties:nil]; #endif } if (data) { if (![_fileManager fileExistsAtPath:_diskCachePath]) { [_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL]; } // get cache Path for image key NSString *cachePathForKey = [self defaultCachePathForKey:key]; // transform to NSUrl NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey]; [_fileManager createFileAtPath:cachePathForKey contents:data attributes:nil]; // disable iCloud backup if (self.shouldDisableiCloud) { [fileURL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:nil]; } } }); } } - (void)storeImage:(UIImage *)image forKey:(NSString *)key { [self storeImage:image recalculateFromImage:YES imageData:nil forKey:key toDisk:YES]; } - (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk { [self storeImage:image recalculateFromImage:YES imageData:nil forKey:key toDisk:toDisk]; } - (BOOL)diskImageExistsWithKey:(NSString *)key { BOOL exists = NO; // this is an exception to access the filemanager on another queue than ioQueue, but we are using the shared instance // from apple docs on NSFileManager: The methods of the shared NSFileManager object can be called from multiple threads safely. exists = [[NSFileManager defaultManager] fileExistsAtPath:[self defaultCachePathForKey:key]]; // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name // checking the key with and without the extension if (!exists) { exists = [[NSFileManager defaultManager] fileExistsAtPath:[[self defaultCachePathForKey:key] stringByDeletingPathExtension]]; } return exists; } - (void)diskImageExistsWithKey:(NSString *)key completion:(SDWebImageCheckCacheCompletionBlock)completionBlock { dispatch_async(_ioQueue, ^{ BOOL exists = [_fileManager fileExistsAtPath:[self defaultCachePathForKey:key]]; // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name // checking the key with and without the extension if (!exists) { exists = [_fileManager fileExistsAtPath:[[self defaultCachePathForKey:key] stringByDeletingPathExtension]]; } if (completionBlock) { dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(exists); }); } }); } - (UIImage *)imageFromMemoryCacheForKey:(NSString *)key { return [self.memCache objectForKey:key]; } - (UIImage *)imageFromDiskCacheForKey:(NSString *)key { // First check the in-memory cache... UIImage *image = [self imageFromMemoryCacheForKey:key]; if (image) { return image; } // Second check the disk cache... UIImage *diskImage = [self diskImageForKey:key]; if (diskImage && self.shouldCacheImagesInMemory) { NSUInteger cost = SDCacheCostForImage(diskImage); [self.memCache setObject:diskImage forKey:key cost:cost]; } return diskImage; } - (NSData *)diskImageDataBySearchingAllPathsForKey:(NSString *)key { NSString *defaultPath = [self defaultCachePathForKey:key]; NSData *data = [NSData dataWithContentsOfFile:defaultPath]; if (data) { return data; } // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name // checking the key with and without the extension data = [NSData dataWithContentsOfFile:[defaultPath stringByDeletingPathExtension]]; if (data) { return data; } NSArray *customPaths = [self.customPaths copy]; for (NSString *path in customPaths) { NSString *filePath = [self cachePathForKey:key inPath:path]; NSData *imageData = [NSData dataWithContentsOfFile:filePath]; if (imageData) { return imageData; } // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name // checking the key with and without the extension imageData = [NSData dataWithContentsOfFile:[filePath stringByDeletingPathExtension]]; if (imageData) { return imageData; } } return nil; } - (UIImage *)diskImageForKey:(NSString *)key { NSData *data = [self diskImageDataBySearchingAllPathsForKey:key]; if (data) { UIImage *image = [UIImage sd_imageWithData:data]; image = [self scaledImageForKey:key image:image]; if (self.shouldDecompressImages) { image = [UIImage decodedImageWithImage:image]; } return image; } else { return nil; } } - (UIImage *)scaledImageForKey:(NSString *)key image:(UIImage *)image { return SDScaledImageForKey(key, image); } - (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock { if (!doneBlock) { return nil; } if (!key) { doneBlock(nil, SDImageCacheTypeNone); return nil; } // First check the in-memory cache... UIImage *image = [self imageFromMemoryCacheForKey:key]; if (image) { doneBlock(image, SDImageCacheTypeMemory); return nil; } NSOperation *operation = [NSOperation new]; dispatch_async(self.ioQueue, ^{ if (operation.isCancelled) { return; } @autoreleasepool { UIImage *diskImage = [self diskImageForKey:key]; if (diskImage && self.shouldCacheImagesInMemory) { NSUInteger cost = SDCacheCostForImage(diskImage); [self.memCache setObject:diskImage forKey:key cost:cost]; } dispatch_async(dispatch_get_main_queue(), ^{ doneBlock(diskImage, SDImageCacheTypeDisk); }); } }); return operation; } - (void)removeImageForKey:(NSString *)key { [self removeImageForKey:key withCompletion:nil]; } - (void)removeImageForKey:(NSString *)key withCompletion:(SDWebImageNoParamsBlock)completion { [self removeImageForKey:key fromDisk:YES withCompletion:completion]; } - (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk { [self removeImageForKey:key fromDisk:fromDisk withCompletion:nil]; } - (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(SDWebImageNoParamsBlock)completion { if (key == nil) { return; } if (self.shouldCacheImagesInMemory) { [self.memCache removeObjectForKey:key]; } if (fromDisk) { dispatch_async(self.ioQueue, ^{ [_fileManager removeItemAtPath:[self defaultCachePathForKey:key] error:nil]; if (completion) { dispatch_async(dispatch_get_main_queue(), ^{ completion(); }); } }); } else if (completion){ completion(); } } - (void)setMaxMemoryCost:(NSUInteger)maxMemoryCost { self.memCache.totalCostLimit = maxMemoryCost; } - (NSUInteger)maxMemoryCost { return self.memCache.totalCostLimit; } - (NSUInteger)maxMemoryCountLimit { return self.memCache.countLimit; } - (void)setMaxMemoryCountLimit:(NSUInteger)maxCountLimit { self.memCache.countLimit = maxCountLimit; } - (void)clearMemory { [self.memCache removeAllObjects]; } - (void)clearDisk { [self clearDiskOnCompletion:nil]; } - (void)clearDiskOnCompletion:(SDWebImageNoParamsBlock)completion { dispatch_async(self.ioQueue, ^{ [_fileManager removeItemAtPath:self.diskCachePath error:nil]; [_fileManager createDirectoryAtPath:self.diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL]; if (completion) { dispatch_async(dispatch_get_main_queue(), ^{ completion(); }); } }); } - (void)cleanDisk { [self cleanDiskWithCompletionBlock:nil]; } - (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock { dispatch_async(self.ioQueue, ^{ NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES]; NSArray *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey]; // This enumerator prefetches useful properties for our cache files. NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL includingPropertiesForKeys:resourceKeys options:NSDirectoryEnumerationSkipsHiddenFiles errorHandler:NULL]; NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.maxCacheAge]; NSMutableDictionary *cacheFiles = [NSMutableDictionary dictionary]; NSUInteger currentCacheSize = 0; // Enumerate all of the files in the cache directory. This loop has two purposes: // // 1. Removing files that are older than the expiration date. // 2. Storing file attributes for the size-based cleanup pass. NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init]; for (NSURL *fileURL in fileEnumerator) { NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:NULL]; // Skip directories. if ([resourceValues[NSURLIsDirectoryKey] boolValue]) { continue; } // Remove files that are older than the expiration date; NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey]; if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) { [urlsToDelete addObject:fileURL]; continue; } // Store a reference to this file and account for its total size. NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey]; currentCacheSize += [totalAllocatedSize unsignedIntegerValue]; [cacheFiles setObject:resourceValues forKey:fileURL]; } for (NSURL *fileURL in urlsToDelete) { [_fileManager removeItemAtURL:fileURL error:nil]; } // If our remaining disk cache exceeds a configured maximum size, perform a second // size-based cleanup pass. We delete the oldest files first. if (self.maxCacheSize > 0 && currentCacheSize > self.maxCacheSize) { // Target half of our maximum cache size for this cleanup pass. const NSUInteger desiredCacheSize = self.maxCacheSize / 2; // Sort the remaining cache files by their last modification time (oldest first). NSArray *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent usingComparator:^NSComparisonResult(id obj1, id obj2) { return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]]; }]; // Delete files until we fall below our desired cache size. for (NSURL *fileURL in sortedFiles) { if ([_fileManager removeItemAtURL:fileURL error:nil]) { NSDictionary *resourceValues = cacheFiles[fileURL]; NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey]; currentCacheSize -= [totalAllocatedSize unsignedIntegerValue]; if (currentCacheSize < desiredCacheSize) { break; } } } } if (completionBlock) { dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(); }); } }); } - (void)backgroundCleanDisk { Class UIApplicationClass = NSClassFromString(@"UIApplication"); if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) { return; } UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)]; __block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{ // Clean up any unfinished task business by marking where you // stopped or ending the task outright. [application endBackgroundTask:bgTask]; bgTask = UIBackgroundTaskInvalid; }]; // Start the long-running task and return immediately. [self cleanDiskWithCompletionBlock:^{ [application endBackgroundTask:bgTask]; bgTask = UIBackgroundTaskInvalid; }]; } - (NSUInteger)getSize { __block NSUInteger size = 0; dispatch_sync(self.ioQueue, ^{ NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:self.diskCachePath]; for (NSString *fileName in fileEnumerator) { NSString *filePath = [self.diskCachePath stringByAppendingPathComponent:fileName]; NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil]; size += [attrs fileSize]; } }); return size; } - (NSUInteger)getDiskCount { __block NSUInteger count = 0; dispatch_sync(self.ioQueue, ^{ NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:self.diskCachePath]; count = [[fileEnumerator allObjects] count]; }); return count; } - (void)calculateSizeWithCompletionBlock:(SDWebImageCalculateSizeBlock)completionBlock { NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES]; dispatch_async(self.ioQueue, ^{ NSUInteger fileCount = 0; NSUInteger totalSize = 0; NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL includingPropertiesForKeys:@[NSFileSize] options:NSDirectoryEnumerationSkipsHiddenFiles errorHandler:NULL]; for (NSURL *fileURL in fileEnumerator) { NSNumber *fileSize; [fileURL getResourceValue:&fileSize forKey:NSURLFileSizeKey error:NULL]; totalSize += [fileSize unsignedIntegerValue]; fileCount += 1; } if (completionBlock) { dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(fileCount, totalSize); }); } }); } @end ================================================ FILE: Example/Pods/SDWebImage/SDWebImage/SDWebImageCompat.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * (c) Jamie Pinkham * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #ifdef __OBJC_GC__ #error SDWebImage does not support Objective-C Garbage Collection #endif #if __IPHONE_OS_VERSION_MIN_REQUIRED != 20000 && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_5_0 #error SDWebImage doesn't support Deployment Target version < 5.0 #endif #if !TARGET_OS_IPHONE #import #ifndef UIImage #define UIImage NSImage #endif #ifndef UIImageView #define UIImageView NSImageView #endif #else #import #endif #ifndef NS_ENUM #define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type #endif #ifndef NS_OPTIONS #define NS_OPTIONS(_type, _name) enum _name : _type _name; enum _name : _type #endif #if OS_OBJECT_USE_OBJC #undef SDDispatchQueueRelease #undef SDDispatchQueueSetterSementics #define SDDispatchQueueRelease(q) #define SDDispatchQueueSetterSementics strong #else #undef SDDispatchQueueRelease #undef SDDispatchQueueSetterSementics #define SDDispatchQueueRelease(q) (dispatch_release(q)) #define SDDispatchQueueSetterSementics assign #endif extern UIImage *SDScaledImageForKey(NSString *key, UIImage *image); typedef void(^SDWebImageNoParamsBlock)(); extern NSString *const SDWebImageErrorDomain; #define dispatch_main_sync_safe(block)\ if ([NSThread isMainThread]) {\ block();\ } else {\ dispatch_sync(dispatch_get_main_queue(), block);\ } #define dispatch_main_async_safe(block)\ if ([NSThread isMainThread]) {\ block();\ } else {\ dispatch_async(dispatch_get_main_queue(), block);\ } ================================================ FILE: Example/Pods/SDWebImage/SDWebImage/SDWebImageCompat.m ================================================ // // SDWebImageCompat.m // SDWebImage // // Created by Olivier Poitrey on 11/12/12. // Copyright (c) 2012 Dailymotion. All rights reserved. // #import "SDWebImageCompat.h" #if !__has_feature(objc_arc) #error SDWebImage is ARC only. Either turn on ARC for the project or use -fobjc-arc flag #endif inline UIImage *SDScaledImageForKey(NSString *key, UIImage *image) { if (!image) { return nil; } if ([image.images count] > 0) { NSMutableArray *scaledImages = [NSMutableArray array]; for (UIImage *tempImage in image.images) { [scaledImages addObject:SDScaledImageForKey(key, tempImage)]; } return [UIImage animatedImageWithImages:scaledImages duration:image.duration]; } else { if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) { CGFloat scale = [UIScreen mainScreen].scale; if (key.length >= 8) { NSRange range = [key rangeOfString:@"@2x."]; if (range.location != NSNotFound) { scale = 2.0; } range = [key rangeOfString:@"@3x."]; if (range.location != NSNotFound) { scale = 3.0; } } UIImage *scaledImage = [[UIImage alloc] initWithCGImage:image.CGImage scale:scale orientation:image.imageOrientation]; image = scaledImage; } return image; } } NSString *const SDWebImageErrorDomain = @"SDWebImageErrorDomain"; ================================================ FILE: Example/Pods/SDWebImage/SDWebImage/SDWebImageDecoder.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * Created by james on 9/28/11. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDWebImageCompat.h" @interface UIImage (ForceDecode) + (UIImage *)decodedImageWithImage:(UIImage *)image; @end ================================================ FILE: Example/Pods/SDWebImage/SDWebImage/SDWebImageDecoder.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * Created by james on 9/28/11. * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageDecoder.h" @implementation UIImage (ForceDecode) + (UIImage *)decodedImageWithImage:(UIImage *)image { // while downloading huge amount of images // autorelease the bitmap context // and all vars to help system to free memory // when there are memory warning. // on iOS7, do not forget to call // [[SDImageCache sharedImageCache] clearMemory]; @autoreleasepool{ // do not decode animated images if (image.images) { return image; } CGImageRef imageRef = image.CGImage; CGImageAlphaInfo alpha = CGImageGetAlphaInfo(imageRef); BOOL anyAlpha = (alpha == kCGImageAlphaFirst || alpha == kCGImageAlphaLast || alpha == kCGImageAlphaPremultipliedFirst || alpha == kCGImageAlphaPremultipliedLast); if (anyAlpha) { return image; } size_t width = CGImageGetWidth(imageRef); size_t height = CGImageGetHeight(imageRef); // current CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(CGImageGetColorSpace(imageRef)); CGColorSpaceRef colorspaceRef = CGImageGetColorSpace(imageRef); bool unsupportedColorSpace = (imageColorSpaceModel == 0 || imageColorSpaceModel == -1 || imageColorSpaceModel == kCGColorSpaceModelCMYK || imageColorSpaceModel == kCGColorSpaceModelIndexed); if (unsupportedColorSpace) colorspaceRef = CGColorSpaceCreateDeviceRGB(); CGContextRef context = CGBitmapContextCreate(NULL, width, height, CGImageGetBitsPerComponent(imageRef), 0, colorspaceRef, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst); // Draw the image into the context and retrieve the new image, which will now have an alpha layer CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); CGImageRef imageRefWithAlpha = CGBitmapContextCreateImage(context); UIImage *imageWithAlpha = [UIImage imageWithCGImage:imageRefWithAlpha scale:image.scale orientation:image.imageOrientation]; if (unsupportedColorSpace) CGColorSpaceRelease(colorspaceRef); CGContextRelease(context); CGImageRelease(imageRefWithAlpha); return imageWithAlpha; } } @end ================================================ FILE: Example/Pods/SDWebImage/SDWebImage/SDWebImageDownloader.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDWebImageCompat.h" #import "SDWebImageOperation.h" typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) { SDWebImageDownloaderLowPriority = 1 << 0, SDWebImageDownloaderProgressiveDownload = 1 << 1, /** * By default, request prevent the of NSURLCache. With this flag, NSURLCache * is used with default policies. */ SDWebImageDownloaderUseNSURLCache = 1 << 2, /** * Call completion block with nil image/imageData if the image was read from NSURLCache * (to be combined with `SDWebImageDownloaderUseNSURLCache`). */ SDWebImageDownloaderIgnoreCachedResponse = 1 << 3, /** * In iOS 4+, continue the download of the image if the app goes to background. This is achieved by asking the system for * extra time in background to let the request finish. If the background task expires the operation will be cancelled. */ SDWebImageDownloaderContinueInBackground = 1 << 4, /** * Handles cookies stored in NSHTTPCookieStore by setting * NSMutableURLRequest.HTTPShouldHandleCookies = YES; */ SDWebImageDownloaderHandleCookies = 1 << 5, /** * Enable to allow untrusted SSL certificates. * Useful for testing purposes. Use with caution in production. */ SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6, /** * Put the image in the high priority queue. */ SDWebImageDownloaderHighPriority = 1 << 7, }; typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) { /** * Default value. All download operations will execute in queue style (first-in-first-out). */ SDWebImageDownloaderFIFOExecutionOrder, /** * All download operations will execute in stack style (last-in-first-out). */ SDWebImageDownloaderLIFOExecutionOrder }; extern NSString *const SDWebImageDownloadStartNotification; extern NSString *const SDWebImageDownloadStopNotification; typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize); typedef void(^SDWebImageDownloaderCompletedBlock)(UIImage *image, NSData *data, NSError *error, BOOL finished); typedef NSDictionary *(^SDWebImageDownloaderHeadersFilterBlock)(NSURL *url, NSDictionary *headers); /** * Asynchronous downloader dedicated and optimized for image loading. */ @interface SDWebImageDownloader : NSObject /** * Decompressing images that are downloaded and cached can improve performance but can consume lot of memory. * Defaults to YES. Set this to NO if you are experiencing a crash due to excessive memory consumption. */ @property (assign, nonatomic) BOOL shouldDecompressImages; @property (assign, nonatomic) NSInteger maxConcurrentDownloads; /** * Shows the current amount of downloads that still need to be downloaded */ @property (readonly, nonatomic) NSUInteger currentDownloadCount; /** * The timeout value (in seconds) for the download operation. Default: 15.0. */ @property (assign, nonatomic) NSTimeInterval downloadTimeout; /** * Changes download operations execution order. Default value is `SDWebImageDownloaderFIFOExecutionOrder`. */ @property (assign, nonatomic) SDWebImageDownloaderExecutionOrder executionOrder; /** * Singleton method, returns the shared instance * * @return global shared instance of downloader class */ + (SDWebImageDownloader *)sharedDownloader; /** * Set the default URL credential to be set for request operations. */ @property (strong, nonatomic) NSURLCredential *urlCredential; /** * Set username */ @property (strong, nonatomic) NSString *username; /** * Set password */ @property (strong, nonatomic) NSString *password; /** * Set filter to pick headers for downloading image HTTP request. * * This block will be invoked for each downloading image request, returned * NSDictionary will be used as headers in corresponding HTTP request. */ @property (nonatomic, copy) SDWebImageDownloaderHeadersFilterBlock headersFilter; /** * Set a value for a HTTP header to be appended to each download HTTP request. * * @param value The value for the header field. Use `nil` value to remove the header. * @param field The name of the header field to set. */ - (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field; /** * Returns the value of the specified HTTP header field. * * @return The value associated with the header field field, or `nil` if there is no corresponding header field. */ - (NSString *)valueForHTTPHeaderField:(NSString *)field; /** * Sets a subclass of `SDWebImageDownloaderOperation` as the default * `NSOperation` to be used each time SDWebImage constructs a request * operation to download an image. * * @param operationClass The subclass of `SDWebImageDownloaderOperation` to set * as default. Passing `nil` will revert to `SDWebImageDownloaderOperation`. */ - (void)setOperationClass:(Class)operationClass; /** * Creates a SDWebImageDownloader async downloader instance with a given URL * * The delegate will be informed when the image is finish downloaded or an error has happen. * * @see SDWebImageDownloaderDelegate * * @param url The URL to the image to download * @param options The options to be used for this download * @param progressBlock A block called repeatedly while the image is downloading * @param completedBlock A block called once the download is completed. * If the download succeeded, the image parameter is set, in case of error, * error parameter is set with the error. The last parameter is always YES * if SDWebImageDownloaderProgressiveDownload isn't use. With the * SDWebImageDownloaderProgressiveDownload option, this block is called * repeatedly with the partial image object and the finished argument set to NO * before to be called a last time with the full image and finished argument * set to YES. In case of error, the finished argument is always YES. * * @return A cancellable SDWebImageOperation */ - (id )downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock; /** * Sets the download queue suspension state */ - (void)setSuspended:(BOOL)suspended; @end ================================================ FILE: Example/Pods/SDWebImage/SDWebImage/SDWebImageDownloader.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageDownloader.h" #import "SDWebImageDownloaderOperation.h" #import static NSString *const kProgressCallbackKey = @"progress"; static NSString *const kCompletedCallbackKey = @"completed"; @interface SDWebImageDownloader () @property (strong, nonatomic) NSOperationQueue *downloadQueue; @property (weak, nonatomic) NSOperation *lastAddedOperation; @property (assign, nonatomic) Class operationClass; @property (strong, nonatomic) NSMutableDictionary *URLCallbacks; @property (strong, nonatomic) NSMutableDictionary *HTTPHeaders; // This queue is used to serialize the handling of the network responses of all the download operation in a single queue @property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue; @end @implementation SDWebImageDownloader + (void)initialize { // Bind SDNetworkActivityIndicator if available (download it here: http://github.com/rs/SDNetworkActivityIndicator ) // To use it, just add #import "SDNetworkActivityIndicator.h" in addition to the SDWebImage import if (NSClassFromString(@"SDNetworkActivityIndicator")) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" id activityIndicator = [NSClassFromString(@"SDNetworkActivityIndicator") performSelector:NSSelectorFromString(@"sharedActivityIndicator")]; #pragma clang diagnostic pop // Remove observer in case it was previously added. [[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStartNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStopNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:activityIndicator selector:NSSelectorFromString(@"startActivity") name:SDWebImageDownloadStartNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:activityIndicator selector:NSSelectorFromString(@"stopActivity") name:SDWebImageDownloadStopNotification object:nil]; } } + (SDWebImageDownloader *)sharedDownloader { static dispatch_once_t once; static id instance; dispatch_once(&once, ^{ instance = [self new]; }); return instance; } - (id)init { if ((self = [super init])) { _operationClass = [SDWebImageDownloaderOperation class]; _shouldDecompressImages = YES; _executionOrder = SDWebImageDownloaderFIFOExecutionOrder; _downloadQueue = [NSOperationQueue new]; _downloadQueue.maxConcurrentOperationCount = 6; _URLCallbacks = [NSMutableDictionary new]; #ifdef SD_WEBP _HTTPHeaders = [@{@"Accept": @"image/webp,image/*;q=0.8"} mutableCopy]; #else _HTTPHeaders = [@{@"Accept": @"image/*;q=0.8"} mutableCopy]; #endif _barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT); _downloadTimeout = 15.0; } return self; } - (void)dealloc { [self.downloadQueue cancelAllOperations]; SDDispatchQueueRelease(_barrierQueue); } - (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field { if (value) { self.HTTPHeaders[field] = value; } else { [self.HTTPHeaders removeObjectForKey:field]; } } - (NSString *)valueForHTTPHeaderField:(NSString *)field { return self.HTTPHeaders[field]; } - (void)setMaxConcurrentDownloads:(NSInteger)maxConcurrentDownloads { _downloadQueue.maxConcurrentOperationCount = maxConcurrentDownloads; } - (NSUInteger)currentDownloadCount { return _downloadQueue.operationCount; } - (NSInteger)maxConcurrentDownloads { return _downloadQueue.maxConcurrentOperationCount; } - (void)setOperationClass:(Class)operationClass { _operationClass = operationClass ?: [SDWebImageDownloaderOperation class]; } - (id )downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock { __block SDWebImageDownloaderOperation *operation; __weak __typeof(self)wself = self; [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^{ NSTimeInterval timeoutInterval = wself.downloadTimeout; if (timeoutInterval == 0.0) { timeoutInterval = 15.0; } // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval]; request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies); request.HTTPShouldUsePipelining = YES; if (wself.headersFilter) { request.allHTTPHeaderFields = wself.headersFilter(url, [wself.HTTPHeaders copy]); } else { request.allHTTPHeaderFields = wself.HTTPHeaders; } operation = [[wself.operationClass alloc] initWithRequest:request options:options progress:^(NSInteger receivedSize, NSInteger expectedSize) { SDWebImageDownloader *sself = wself; if (!sself) return; __block NSArray *callbacksForURL; dispatch_sync(sself.barrierQueue, ^{ callbacksForURL = [sself.URLCallbacks[url] copy]; }); for (NSDictionary *callbacks in callbacksForURL) { dispatch_async(dispatch_get_main_queue(), ^{ SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey]; if (callback) callback(receivedSize, expectedSize); }); } } completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) { SDWebImageDownloader *sself = wself; if (!sself) return; __block NSArray *callbacksForURL; dispatch_barrier_sync(sself.barrierQueue, ^{ callbacksForURL = [sself.URLCallbacks[url] copy]; if (finished) { [sself.URLCallbacks removeObjectForKey:url]; } }); for (NSDictionary *callbacks in callbacksForURL) { SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey]; if (callback) callback(image, data, error, finished); } } cancelled:^{ SDWebImageDownloader *sself = wself; if (!sself) return; dispatch_barrier_async(sself.barrierQueue, ^{ [sself.URLCallbacks removeObjectForKey:url]; }); }]; operation.shouldDecompressImages = wself.shouldDecompressImages; if (wself.urlCredential) { operation.credential = wself.urlCredential; } else if (wself.username && wself.password) { operation.credential = [NSURLCredential credentialWithUser:wself.username password:wself.password persistence:NSURLCredentialPersistenceForSession]; } if (options & SDWebImageDownloaderHighPriority) { operation.queuePriority = NSOperationQueuePriorityHigh; } else if (options & SDWebImageDownloaderLowPriority) { operation.queuePriority = NSOperationQueuePriorityLow; } [wself.downloadQueue addOperation:operation]; if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) { // Emulate LIFO execution order by systematically adding new operations as last operation's dependency [wself.lastAddedOperation addDependency:operation]; wself.lastAddedOperation = operation; } }]; return operation; } - (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL *)url createCallback:(SDWebImageNoParamsBlock)createCallback { // The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data. if (url == nil) { if (completedBlock != nil) { completedBlock(nil, nil, nil, NO); } return; } dispatch_barrier_sync(self.barrierQueue, ^{ BOOL first = NO; if (!self.URLCallbacks[url]) { self.URLCallbacks[url] = [NSMutableArray new]; first = YES; } // Handle single download of simultaneous download request for the same URL NSMutableArray *callbacksForURL = self.URLCallbacks[url]; NSMutableDictionary *callbacks = [NSMutableDictionary new]; if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy]; if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy]; [callbacksForURL addObject:callbacks]; self.URLCallbacks[url] = callbacksForURL; if (first) { createCallback(); } }); } - (void)setSuspended:(BOOL)suspended { [self.downloadQueue setSuspended:suspended]; } @end ================================================ FILE: Example/Pods/SDWebImage/SDWebImage/SDWebImageDownloaderOperation.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDWebImageDownloader.h" #import "SDWebImageOperation.h" extern NSString *const SDWebImageDownloadStartNotification; extern NSString *const SDWebImageDownloadReceiveResponseNotification; extern NSString *const SDWebImageDownloadStopNotification; extern NSString *const SDWebImageDownloadFinishNotification; @interface SDWebImageDownloaderOperation : NSOperation /** * The request used by the operation's connection. */ @property (strong, nonatomic, readonly) NSURLRequest *request; @property (assign, nonatomic) BOOL shouldDecompressImages; /** * Whether the URL connection should consult the credential storage for authenticating the connection. `YES` by default. * * This is the value that is returned in the `NSURLConnectionDelegate` method `-connectionShouldUseCredentialStorage:`. */ @property (nonatomic, assign) BOOL shouldUseCredentialStorage; /** * The credential used for authentication challenges in `-connection:didReceiveAuthenticationChallenge:`. * * This will be overridden by any shared credentials that exist for the username or password of the request URL, if present. */ @property (nonatomic, strong) NSURLCredential *credential; /** * The SDWebImageDownloaderOptions for the receiver. */ @property (assign, nonatomic, readonly) SDWebImageDownloaderOptions options; /** * The expected size of data. */ @property (assign, nonatomic) NSInteger expectedSize; /** * The response returned by the operation's connection. */ @property (strong, nonatomic) NSURLResponse *response; /** * Initializes a `SDWebImageDownloaderOperation` object * * @see SDWebImageDownloaderOperation * * @param request the URL request * @param options downloader options * @param progressBlock the block executed when a new chunk of data arrives. * @note the progress block is executed on a background queue * @param completedBlock the block executed when the download is done. * @note the completed block is executed on the main queue for success. If errors are found, there is a chance the block will be executed on a background queue * @param cancelBlock the block executed if the download (operation) is cancelled * * @return the initialized instance */ - (id)initWithRequest:(NSURLRequest *)request options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock cancelled:(SDWebImageNoParamsBlock)cancelBlock; @end ================================================ FILE: Example/Pods/SDWebImage/SDWebImage/SDWebImageDownloaderOperation.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageDownloaderOperation.h" #import "SDWebImageDecoder.h" #import "UIImage+MultiFormat.h" #import #import "SDWebImageManager.h" NSString *const SDWebImageDownloadStartNotification = @"SDWebImageDownloadStartNotification"; NSString *const SDWebImageDownloadReceiveResponseNotification = @"SDWebImageDownloadReceiveResponseNotification"; NSString *const SDWebImageDownloadStopNotification = @"SDWebImageDownloadStopNotification"; NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinishNotification"; @interface SDWebImageDownloaderOperation () @property (copy, nonatomic) SDWebImageDownloaderProgressBlock progressBlock; @property (copy, nonatomic) SDWebImageDownloaderCompletedBlock completedBlock; @property (copy, nonatomic) SDWebImageNoParamsBlock cancelBlock; @property (assign, nonatomic, getter = isExecuting) BOOL executing; @property (assign, nonatomic, getter = isFinished) BOOL finished; @property (strong, nonatomic) NSMutableData *imageData; @property (strong, nonatomic) NSURLConnection *connection; @property (strong, atomic) NSThread *thread; #if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0 @property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId; #endif @end @implementation SDWebImageDownloaderOperation { size_t width, height; UIImageOrientation orientation; BOOL responseFromCached; } @synthesize executing = _executing; @synthesize finished = _finished; - (id)initWithRequest:(NSURLRequest *)request options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock cancelled:(SDWebImageNoParamsBlock)cancelBlock { if ((self = [super init])) { _request = request; _shouldDecompressImages = YES; _shouldUseCredentialStorage = YES; _options = options; _progressBlock = [progressBlock copy]; _completedBlock = [completedBlock copy]; _cancelBlock = [cancelBlock copy]; _executing = NO; _finished = NO; _expectedSize = 0; responseFromCached = YES; // Initially wrong until `connection:willCacheResponse:` is called or not called } return self; } - (void)start { @synchronized (self) { if (self.isCancelled) { self.finished = YES; [self reset]; return; } #if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0 Class UIApplicationClass = NSClassFromString(@"UIApplication"); BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)]; if (hasApplication && [self shouldContinueWhenAppEntersBackground]) { __weak __typeof__ (self) wself = self; UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)]; self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{ __strong __typeof (wself) sself = wself; if (sself) { [sself cancel]; [app endBackgroundTask:sself.backgroundTaskId]; sself.backgroundTaskId = UIBackgroundTaskInvalid; } }]; } #endif self.executing = YES; self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO]; self.thread = [NSThread currentThread]; } [self.connection start]; if (self.connection) { if (self.progressBlock) { self.progressBlock(0, NSURLResponseUnknownLength); } dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self]; }); if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_5_1) { // Make sure to run the runloop in our background thread so it can process downloaded data // Note: we use a timeout to work around an issue with NSURLConnection cancel under iOS 5 // not waking up the runloop, leading to dead threads (see https://github.com/rs/SDWebImage/issues/466) CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, false); } else { CFRunLoopRun(); } if (!self.isFinished) { [self.connection cancel]; [self connection:self.connection didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorTimedOut userInfo:@{NSURLErrorFailingURLErrorKey : self.request.URL}]]; } } else { if (self.completedBlock) { self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}], YES); } } #if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0 Class UIApplicationClass = NSClassFromString(@"UIApplication"); if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) { return; } if (self.backgroundTaskId != UIBackgroundTaskInvalid) { UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)]; [app endBackgroundTask:self.backgroundTaskId]; self.backgroundTaskId = UIBackgroundTaskInvalid; } #endif } - (void)cancel { @synchronized (self) { if (self.thread) { [self performSelector:@selector(cancelInternalAndStop) onThread:self.thread withObject:nil waitUntilDone:NO]; } else { [self cancelInternal]; } } } - (void)cancelInternalAndStop { if (self.isFinished) return; [self cancelInternal]; CFRunLoopStop(CFRunLoopGetCurrent()); } - (void)cancelInternal { if (self.isFinished) return; [super cancel]; if (self.cancelBlock) self.cancelBlock(); if (self.connection) { [self.connection cancel]; dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self]; }); // As we cancelled the connection, its callback won't be called and thus won't // maintain the isFinished and isExecuting flags. if (self.isExecuting) self.executing = NO; if (!self.isFinished) self.finished = YES; } [self reset]; } - (void)done { self.finished = YES; self.executing = NO; [self reset]; } - (void)reset { self.cancelBlock = nil; self.completedBlock = nil; self.progressBlock = nil; self.connection = nil; self.imageData = nil; self.thread = nil; } - (void)setFinished:(BOOL)finished { [self willChangeValueForKey:@"isFinished"]; _finished = finished; [self didChangeValueForKey:@"isFinished"]; } - (void)setExecuting:(BOOL)executing { [self willChangeValueForKey:@"isExecuting"]; _executing = executing; [self didChangeValueForKey:@"isExecuting"]; } - (BOOL)isConcurrent { return YES; } #pragma mark NSURLConnection (delegate) - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { //'304 Not Modified' is an exceptional one if (![response respondsToSelector:@selector(statusCode)] || ([((NSHTTPURLResponse *)response) statusCode] < 400 && [((NSHTTPURLResponse *)response) statusCode] != 304)) { NSInteger expected = response.expectedContentLength > 0 ? (NSInteger)response.expectedContentLength : 0; self.expectedSize = expected; if (self.progressBlock) { self.progressBlock(0, expected); } self.imageData = [[NSMutableData alloc] initWithCapacity:expected]; self.response = response; dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:self]; }); } else { NSUInteger code = [((NSHTTPURLResponse *)response) statusCode]; //This is the case when server returns '304 Not Modified'. It means that remote image is not changed. //In case of 304 we need just cancel the operation and return cached image from the cache. if (code == 304) { [self cancelInternal]; } else { [self.connection cancel]; } dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self]; }); if (self.completedBlock) { self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:[((NSHTTPURLResponse *)response) statusCode] userInfo:nil], YES); } CFRunLoopStop(CFRunLoopGetCurrent()); [self done]; } } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { [self.imageData appendData:data]; if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0 && self.completedBlock) { // The following code is from http://www.cocoaintheshell.com/2011/05/progressive-images-download-imageio/ // Thanks to the author @Nyx0uf // Get the total bytes downloaded const NSInteger totalSize = self.imageData.length; // Update the data source, we must pass ALL the data, not just the new bytes CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)self.imageData, NULL); if (width + height == 0) { CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL); if (properties) { NSInteger orientationValue = -1; CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight); if (val) CFNumberGetValue(val, kCFNumberLongType, &height); val = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth); if (val) CFNumberGetValue(val, kCFNumberLongType, &width); val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation); if (val) CFNumberGetValue(val, kCFNumberNSIntegerType, &orientationValue); CFRelease(properties); // When we draw to Core Graphics, we lose orientation information, // which means the image below born of initWithCGIImage will be // oriented incorrectly sometimes. (Unlike the image born of initWithData // in connectionDidFinishLoading.) So save it here and pass it on later. orientation = [[self class] orientationFromPropertyValue:(orientationValue == -1 ? 1 : orientationValue)]; } } if (width + height > 0 && totalSize < self.expectedSize) { // Create the image CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL); #ifdef TARGET_OS_IPHONE // Workaround for iOS anamorphic image if (partialImageRef) { const size_t partialHeight = CGImageGetHeight(partialImageRef); CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef bmContext = CGBitmapContextCreate(NULL, width, height, 8, width * 4, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst); CGColorSpaceRelease(colorSpace); if (bmContext) { CGContextDrawImage(bmContext, (CGRect){.origin.x = 0.0f, .origin.y = 0.0f, .size.width = width, .size.height = partialHeight}, partialImageRef); CGImageRelease(partialImageRef); partialImageRef = CGBitmapContextCreateImage(bmContext); CGContextRelease(bmContext); } else { CGImageRelease(partialImageRef); partialImageRef = nil; } } #endif if (partialImageRef) { UIImage *image = [UIImage imageWithCGImage:partialImageRef scale:1 orientation:orientation]; NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL]; UIImage *scaledImage = [self scaledImageForKey:key image:image]; if (self.shouldDecompressImages) { image = [UIImage decodedImageWithImage:scaledImage]; } else { image = scaledImage; } CGImageRelease(partialImageRef); dispatch_main_sync_safe(^{ if (self.completedBlock) { self.completedBlock(image, nil, nil, NO); } }); } } CFRelease(imageSource); } if (self.progressBlock) { self.progressBlock(self.imageData.length, self.expectedSize); } } + (UIImageOrientation)orientationFromPropertyValue:(NSInteger)value { switch (value) { case 1: return UIImageOrientationUp; case 3: return UIImageOrientationDown; case 8: return UIImageOrientationLeft; case 6: return UIImageOrientationRight; case 2: return UIImageOrientationUpMirrored; case 4: return UIImageOrientationDownMirrored; case 5: return UIImageOrientationLeftMirrored; case 7: return UIImageOrientationRightMirrored; default: return UIImageOrientationUp; } } - (UIImage *)scaledImageForKey:(NSString *)key image:(UIImage *)image { return SDScaledImageForKey(key, image); } - (void)connectionDidFinishLoading:(NSURLConnection *)aConnection { SDWebImageDownloaderCompletedBlock completionBlock = self.completedBlock; @synchronized(self) { CFRunLoopStop(CFRunLoopGetCurrent()); self.thread = nil; self.connection = nil; dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self]; [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:self]; }); } if (![[NSURLCache sharedURLCache] cachedResponseForRequest:_request]) { responseFromCached = NO; } if (completionBlock) { if (self.options & SDWebImageDownloaderIgnoreCachedResponse && responseFromCached) { completionBlock(nil, nil, nil, YES); } else if (self.imageData) { UIImage *image = [UIImage sd_imageWithData:self.imageData]; NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL]; image = [self scaledImageForKey:key image:image]; // Do not force decoding animated GIFs if (!image.images) { if (self.shouldDecompressImages) { image = [UIImage decodedImageWithImage:image]; } } if (CGSizeEqualToSize(image.size, CGSizeZero)) { completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}], YES); } else { completionBlock(image, self.imageData, nil, YES); } } else { completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}], YES); } } self.completionBlock = nil; [self done]; } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { @synchronized(self) { CFRunLoopStop(CFRunLoopGetCurrent()); self.thread = nil; self.connection = nil; dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self]; }); } if (self.completedBlock) { self.completedBlock(nil, nil, error, YES); } self.completionBlock = nil; [self done]; } - (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse { responseFromCached = NO; // If this method is called, it means the response wasn't read from cache if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) { // Prevents caching of responses return nil; } else { return cachedResponse; } } - (BOOL)shouldContinueWhenAppEntersBackground { return self.options & SDWebImageDownloaderContinueInBackground; } - (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection __unused *)connection { return self.shouldUseCredentialStorage; } - (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{ if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { if (!(self.options & SDWebImageDownloaderAllowInvalidSSLCertificates) && [challenge.sender respondsToSelector:@selector(performDefaultHandlingForAuthenticationChallenge:)]) { [challenge.sender performDefaultHandlingForAuthenticationChallenge:challenge]; } else { NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge]; } } else { if ([challenge previousFailureCount] == 0) { if (self.credential) { [[challenge sender] useCredential:self.credential forAuthenticationChallenge:challenge]; } else { [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge]; } } else { [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge]; } } } @end ================================================ FILE: Example/Pods/SDWebImage/SDWebImage/SDWebImageManager.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" #import "SDWebImageOperation.h" #import "SDWebImageDownloader.h" #import "SDImageCache.h" typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) { /** * By default, when a URL fail to be downloaded, the URL is blacklisted so the library won't keep trying. * This flag disable this blacklisting. */ SDWebImageRetryFailed = 1 << 0, /** * By default, image downloads are started during UI interactions, this flags disable this feature, * leading to delayed download on UIScrollView deceleration for instance. */ SDWebImageLowPriority = 1 << 1, /** * This flag disables on-disk caching */ SDWebImageCacheMemoryOnly = 1 << 2, /** * This flag enables progressive download, the image is displayed progressively during download as a browser would do. * By default, the image is only displayed once completely downloaded. */ SDWebImageProgressiveDownload = 1 << 3, /** * Even if the image is cached, respect the HTTP response cache control, and refresh the image from remote location if needed. * The disk caching will be handled by NSURLCache instead of SDWebImage leading to slight performance degradation. * This option helps deal with images changing behind the same request URL, e.g. Facebook graph api profile pics. * If a cached image is refreshed, the completion block is called once with the cached image and again with the final image. * * Use this flag only if you can't make your URLs static with embedded cache busting parameter. */ SDWebImageRefreshCached = 1 << 4, /** * In iOS 4+, continue the download of the image if the app goes to background. This is achieved by asking the system for * extra time in background to let the request finish. If the background task expires the operation will be cancelled. */ SDWebImageContinueInBackground = 1 << 5, /** * Handles cookies stored in NSHTTPCookieStore by setting * NSMutableURLRequest.HTTPShouldHandleCookies = YES; */ SDWebImageHandleCookies = 1 << 6, /** * Enable to allow untrusted SSL certificates. * Useful for testing purposes. Use with caution in production. */ SDWebImageAllowInvalidSSLCertificates = 1 << 7, /** * By default, image are loaded in the order they were queued. This flag move them to * the front of the queue and is loaded immediately instead of waiting for the current queue to be loaded (which * could take a while). */ SDWebImageHighPriority = 1 << 8, /** * By default, placeholder images are loaded while the image is loading. This flag will delay the loading * of the placeholder image until after the image has finished loading. */ SDWebImageDelayPlaceholder = 1 << 9, /** * We usually don't call transformDownloadedImage delegate method on animated images, * as most transformation code would mangle it. * Use this flag to transform them anyway. */ SDWebImageTransformAnimatedImage = 1 << 10, /** * By default, image is added to the imageView after download. But in some cases, we want to * have the hand before setting the image (apply a filter or add it with cross-fade animation for instance) * Use this flag if you want to manually set the image in the completion when success */ SDWebImageAvoidAutoSetImage = 1 << 11 }; typedef void(^SDWebImageCompletionBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL); typedef void(^SDWebImageCompletionWithFinishedBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL); typedef NSString *(^SDWebImageCacheKeyFilterBlock)(NSURL *url); @class SDWebImageManager; @protocol SDWebImageManagerDelegate @optional /** * Controls which image should be downloaded when the image is not found in the cache. * * @param imageManager The current `SDWebImageManager` * @param imageURL The url of the image to be downloaded * * @return Return NO to prevent the downloading of the image on cache misses. If not implemented, YES is implied. */ - (BOOL)imageManager:(SDWebImageManager *)imageManager shouldDownloadImageForURL:(NSURL *)imageURL; /** * Allows to transform the image immediately after it has been downloaded and just before to cache it on disk and memory. * NOTE: This method is called from a global queue in order to not to block the main thread. * * @param imageManager The current `SDWebImageManager` * @param image The image to transform * @param imageURL The url of the image to transform * * @return The transformed image object. */ - (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL; @end /** * The SDWebImageManager is the class behind the UIImageView+WebCache category and likes. * It ties the asynchronous downloader (SDWebImageDownloader) with the image cache store (SDImageCache). * You can use this class directly to benefit from web image downloading with caching in another context than * a UIView. * * Here is a simple example of how to use SDWebImageManager: * * @code SDWebImageManager *manager = [SDWebImageManager sharedManager]; [manager downloadImageWithURL:imageURL options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { if (image) { // do something with image } }]; * @endcode */ @interface SDWebImageManager : NSObject @property (weak, nonatomic) id delegate; @property (strong, nonatomic, readonly) SDImageCache *imageCache; @property (strong, nonatomic, readonly) SDWebImageDownloader *imageDownloader; /** * The cache filter is a block used each time SDWebImageManager need to convert an URL into a cache key. This can * be used to remove dynamic part of an image URL. * * The following example sets a filter in the application delegate that will remove any query-string from the * URL before to use it as a cache key: * * @code [[SDWebImageManager sharedManager] setCacheKeyFilter:^(NSURL *url) { url = [[NSURL alloc] initWithScheme:url.scheme host:url.host path:url.path]; return [url absoluteString]; }]; * @endcode */ @property (nonatomic, copy) SDWebImageCacheKeyFilterBlock cacheKeyFilter; /** * Returns global SDWebImageManager instance. * * @return SDWebImageManager shared instance */ + (SDWebImageManager *)sharedManager; /** * Downloads the image at the given URL if not present in cache or return the cached version otherwise. * * @param url The URL to the image * @param options A mask to specify options to use for this request * @param progressBlock A block called while image is downloading * @param completedBlock A block called when operation has been completed. * * This parameter is required. * * This block has no return value and takes the requested UIImage as first parameter. * In case of error the image parameter is nil and the second parameter may contain an NSError. * * The third parameter is an `SDImageCacheType` enum indicating if the image was retrieved from the local cache * or from the memory cache or from the network. * * The last parameter is set to NO when the SDWebImageProgressiveDownload option is used and the image is * downloading. This block is thus called repeatedly with a partial image. When image is fully downloaded, the * block is called a last time with the full image and the last parameter set to YES. * * @return Returns an NSObject conforming to SDWebImageOperation. Should be an instance of SDWebImageDownloaderOperation */ - (id )downloadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionWithFinishedBlock)completedBlock; /** * Saves image to cache for given URL * * @param image The image to cache * @param url The URL to the image * */ - (void)saveImageToCache:(UIImage *)image forURL:(NSURL *)url; /** * Cancel all current operations */ - (void)cancelAll; /** * Check one or more operations running */ - (BOOL)isRunning; /** * Check if image has already been cached * * @param url image url * * @return if the image was already cached */ - (BOOL)cachedImageExistsForURL:(NSURL *)url; /** * Check if image has already been cached on disk only * * @param url image url * * @return if the image was already cached (disk only) */ - (BOOL)diskImageExistsForURL:(NSURL *)url; /** * Async check if image has already been cached * * @param url image url * @param completionBlock the block to be executed when the check is finished * * @note the completion block is always executed on the main queue */ - (void)cachedImageExistsForURL:(NSURL *)url completion:(SDWebImageCheckCacheCompletionBlock)completionBlock; /** * Async check if image has already been cached on disk only * * @param url image url * @param completionBlock the block to be executed when the check is finished * * @note the completion block is always executed on the main queue */ - (void)diskImageExistsForURL:(NSURL *)url completion:(SDWebImageCheckCacheCompletionBlock)completionBlock; /** *Return the cache key for a given URL */ - (NSString *)cacheKeyForURL:(NSURL *)url; @end #pragma mark - Deprecated typedef void(^SDWebImageCompletedBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType) __deprecated_msg("Block type deprecated. Use `SDWebImageCompletionBlock`"); typedef void(^SDWebImageCompletedWithFinishedBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished) __deprecated_msg("Block type deprecated. Use `SDWebImageCompletionWithFinishedBlock`"); @interface SDWebImageManager (Deprecated) /** * Downloads the image at the given URL if not present in cache or return the cached version otherwise. * * @deprecated This method has been deprecated. Use `downloadImageWithURL:options:progress:completed:` */ - (id )downloadWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletedWithFinishedBlock)completedBlock __deprecated_msg("Method deprecated. Use `downloadImageWithURL:options:progress:completed:`"); @end ================================================ FILE: Example/Pods/SDWebImage/SDWebImage/SDWebImageManager.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageManager.h" #import @interface SDWebImageCombinedOperation : NSObject @property (assign, nonatomic, getter = isCancelled) BOOL cancelled; @property (copy, nonatomic) SDWebImageNoParamsBlock cancelBlock; @property (strong, nonatomic) NSOperation *cacheOperation; @end @interface SDWebImageManager () @property (strong, nonatomic, readwrite) SDImageCache *imageCache; @property (strong, nonatomic, readwrite) SDWebImageDownloader *imageDownloader; @property (strong, nonatomic) NSMutableSet *failedURLs; @property (strong, nonatomic) NSMutableArray *runningOperations; @end @implementation SDWebImageManager + (id)sharedManager { static dispatch_once_t once; static id instance; dispatch_once(&once, ^{ instance = [self new]; }); return instance; } - (id)init { if ((self = [super init])) { _imageCache = [self createCache]; _imageDownloader = [SDWebImageDownloader sharedDownloader]; _failedURLs = [NSMutableSet new]; _runningOperations = [NSMutableArray new]; } return self; } - (SDImageCache *)createCache { return [SDImageCache sharedImageCache]; } - (NSString *)cacheKeyForURL:(NSURL *)url { if (self.cacheKeyFilter) { return self.cacheKeyFilter(url); } else { return [url absoluteString]; } } - (BOOL)cachedImageExistsForURL:(NSURL *)url { NSString *key = [self cacheKeyForURL:url]; if ([self.imageCache imageFromMemoryCacheForKey:key] != nil) return YES; return [self.imageCache diskImageExistsWithKey:key]; } - (BOOL)diskImageExistsForURL:(NSURL *)url { NSString *key = [self cacheKeyForURL:url]; return [self.imageCache diskImageExistsWithKey:key]; } - (void)cachedImageExistsForURL:(NSURL *)url completion:(SDWebImageCheckCacheCompletionBlock)completionBlock { NSString *key = [self cacheKeyForURL:url]; BOOL isInMemoryCache = ([self.imageCache imageFromMemoryCacheForKey:key] != nil); if (isInMemoryCache) { // making sure we call the completion block on the main queue dispatch_async(dispatch_get_main_queue(), ^{ if (completionBlock) { completionBlock(YES); } }); return; } [self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) { // the completion block of checkDiskCacheForImageWithKey:completion: is always called on the main queue, no need to further dispatch if (completionBlock) { completionBlock(isInDiskCache); } }]; } - (void)diskImageExistsForURL:(NSURL *)url completion:(SDWebImageCheckCacheCompletionBlock)completionBlock { NSString *key = [self cacheKeyForURL:url]; [self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) { // the completion block of checkDiskCacheForImageWithKey:completion: is always called on the main queue, no need to further dispatch if (completionBlock) { completionBlock(isInDiskCache); } }]; } - (id )downloadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionWithFinishedBlock)completedBlock { // Invoking this method without a completedBlock is pointless NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead"); // Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, XCode won't // throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString. if ([url isKindOfClass:NSString.class]) { url = [NSURL URLWithString:(NSString *)url]; } // Prevents app crashing on argument type error like sending NSNull instead of NSURL if (![url isKindOfClass:NSURL.class]) { url = nil; } __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new]; __weak SDWebImageCombinedOperation *weakOperation = operation; BOOL isFailedUrl = NO; @synchronized (self.failedURLs) { isFailedUrl = [self.failedURLs containsObject:url]; } if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) { dispatch_main_sync_safe(^{ NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil]; completedBlock(nil, error, SDImageCacheTypeNone, YES, url); }); return operation; } @synchronized (self.runningOperations) { [self.runningOperations addObject:operation]; } NSString *key = [self cacheKeyForURL:url]; operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) { if (operation.isCancelled) { @synchronized (self.runningOperations) { [self.runningOperations removeObject:operation]; } return; } if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) { if (image && options & SDWebImageRefreshCached) { dispatch_main_sync_safe(^{ // If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image // AND try to re-download it in order to let a chance to NSURLCache to refresh it from server. completedBlock(image, nil, cacheType, YES, url); }); } // download if no image or requested to refresh anyway, and download allowed by delegate SDWebImageDownloaderOptions downloaderOptions = 0; if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority; if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload; if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache; if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground; if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies; if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates; if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority; if (image && options & SDWebImageRefreshCached) { // force progressive off if image already cached but forced refreshing downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload; // ignore image read from NSURLCache if image if cached but force refreshing downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse; } id subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) { __strong __typeof(weakOperation) strongOperation = weakOperation; if (!strongOperation || strongOperation.isCancelled) { // Do nothing if the operation was cancelled // See #699 for more details // if we would call the completedBlock, there could be a race condition between this block and another completedBlock for the same object, so if this one is called second, we will overwrite the new data } else if (error) { dispatch_main_sync_safe(^{ if (strongOperation && !strongOperation.isCancelled) { completedBlock(nil, error, SDImageCacheTypeNone, finished, url); } }); if ( error.code != NSURLErrorNotConnectedToInternet && error.code != NSURLErrorCancelled && error.code != NSURLErrorTimedOut && error.code != NSURLErrorInternationalRoamingOff && error.code != NSURLErrorDataNotAllowed && error.code != NSURLErrorCannotFindHost && error.code != NSURLErrorCannotConnectToHost) { @synchronized (self.failedURLs) { [self.failedURLs addObject:url]; } } } else { if ((options & SDWebImageRetryFailed)) { @synchronized (self.failedURLs) { [self.failedURLs removeObject:url]; } } BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly); if (options & SDWebImageRefreshCached && image && !downloadedImage) { // Image refresh hit the NSURLCache cache, do not call the completion block } else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url]; if (transformedImage && finished) { BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage]; [self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk]; } dispatch_main_sync_safe(^{ if (strongOperation && !strongOperation.isCancelled) { completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url); } }); }); } else { if (downloadedImage && finished) { [self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk]; } dispatch_main_sync_safe(^{ if (strongOperation && !strongOperation.isCancelled) { completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url); } }); } } if (finished) { @synchronized (self.runningOperations) { if (strongOperation) { [self.runningOperations removeObject:strongOperation]; } } } }]; operation.cancelBlock = ^{ [subOperation cancel]; @synchronized (self.runningOperations) { __strong __typeof(weakOperation) strongOperation = weakOperation; if (strongOperation) { [self.runningOperations removeObject:strongOperation]; } } }; } else if (image) { dispatch_main_sync_safe(^{ __strong __typeof(weakOperation) strongOperation = weakOperation; if (strongOperation && !strongOperation.isCancelled) { completedBlock(image, nil, cacheType, YES, url); } }); @synchronized (self.runningOperations) { [self.runningOperations removeObject:operation]; } } else { // Image not in cache and download disallowed by delegate dispatch_main_sync_safe(^{ __strong __typeof(weakOperation) strongOperation = weakOperation; if (strongOperation && !weakOperation.isCancelled) { completedBlock(nil, nil, SDImageCacheTypeNone, YES, url); } }); @synchronized (self.runningOperations) { [self.runningOperations removeObject:operation]; } } }]; return operation; } - (void)saveImageToCache:(UIImage *)image forURL:(NSURL *)url { if (image && url) { NSString *key = [self cacheKeyForURL:url]; [self.imageCache storeImage:image forKey:key toDisk:YES]; } } - (void)cancelAll { @synchronized (self.runningOperations) { NSArray *copiedOperations = [self.runningOperations copy]; [copiedOperations makeObjectsPerformSelector:@selector(cancel)]; [self.runningOperations removeObjectsInArray:copiedOperations]; } } - (BOOL)isRunning { BOOL isRunning = NO; @synchronized(self.runningOperations) { isRunning = (self.runningOperations.count > 0); } return isRunning; } @end @implementation SDWebImageCombinedOperation - (void)setCancelBlock:(SDWebImageNoParamsBlock)cancelBlock { // check if the operation is already cancelled, then we just call the cancelBlock if (self.isCancelled) { if (cancelBlock) { cancelBlock(); } _cancelBlock = nil; // don't forget to nil the cancelBlock, otherwise we will get crashes } else { _cancelBlock = [cancelBlock copy]; } } - (void)cancel { self.cancelled = YES; if (self.cacheOperation) { [self.cacheOperation cancel]; self.cacheOperation = nil; } if (self.cancelBlock) { self.cancelBlock(); // TODO: this is a temporary fix to #809. // Until we can figure the exact cause of the crash, going with the ivar instead of the setter // self.cancelBlock = nil; _cancelBlock = nil; } } @end @implementation SDWebImageManager (Deprecated) // deprecated method, uses the non deprecated method // adapter for the completion block - (id )downloadWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletedWithFinishedBlock)completedBlock { return [self downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { if (completedBlock) { completedBlock(image, error, cacheType, finished); } }]; } @end ================================================ FILE: Example/Pods/SDWebImage/SDWebImage/SDWebImageOperation.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import @protocol SDWebImageOperation - (void)cancel; @end ================================================ FILE: Example/Pods/SDWebImage/SDWebImage/SDWebImagePrefetcher.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDWebImageManager.h" @class SDWebImagePrefetcher; @protocol SDWebImagePrefetcherDelegate @optional /** * Called when an image was prefetched. * * @param imagePrefetcher The current image prefetcher * @param imageURL The image url that was prefetched * @param finishedCount The total number of images that were prefetched (successful or not) * @param totalCount The total number of images that were to be prefetched */ - (void)imagePrefetcher:(SDWebImagePrefetcher *)imagePrefetcher didPrefetchURL:(NSURL *)imageURL finishedCount:(NSUInteger)finishedCount totalCount:(NSUInteger)totalCount; /** * Called when all images are prefetched. * @param imagePrefetcher The current image prefetcher * @param totalCount The total number of images that were prefetched (whether successful or not) * @param skippedCount The total number of images that were skipped */ - (void)imagePrefetcher:(SDWebImagePrefetcher *)imagePrefetcher didFinishWithTotalCount:(NSUInteger)totalCount skippedCount:(NSUInteger)skippedCount; @end typedef void(^SDWebImagePrefetcherProgressBlock)(NSUInteger noOfFinishedUrls, NSUInteger noOfTotalUrls); typedef void(^SDWebImagePrefetcherCompletionBlock)(NSUInteger noOfFinishedUrls, NSUInteger noOfSkippedUrls); /** * Prefetch some URLs in the cache for future use. Images are downloaded in low priority. */ @interface SDWebImagePrefetcher : NSObject /** * The web image manager */ @property (strong, nonatomic, readonly) SDWebImageManager *manager; /** * Maximum number of URLs to prefetch at the same time. Defaults to 3. */ @property (nonatomic, assign) NSUInteger maxConcurrentDownloads; /** * SDWebImageOptions for prefetcher. Defaults to SDWebImageLowPriority. */ @property (nonatomic, assign) SDWebImageOptions options; /** * Queue options for Prefetcher. Defaults to Main Queue. */ @property (nonatomic, assign) dispatch_queue_t prefetcherQueue; @property (weak, nonatomic) id delegate; /** * Return the global image prefetcher instance. */ + (SDWebImagePrefetcher *)sharedImagePrefetcher; /** * Allows you to instantiate a prefetcher with any arbitrary image manager. */ - (id)initWithImageManager:(SDWebImageManager *)manager; /** * Assign list of URLs to let SDWebImagePrefetcher to queue the prefetching, * currently one image is downloaded at a time, * and skips images for failed downloads and proceed to the next image in the list * * @param urls list of URLs to prefetch */ - (void)prefetchURLs:(NSArray *)urls; /** * Assign list of URLs to let SDWebImagePrefetcher to queue the prefetching, * currently one image is downloaded at a time, * and skips images for failed downloads and proceed to the next image in the list * * @param urls list of URLs to prefetch * @param progressBlock block to be called when progress updates; * first parameter is the number of completed (successful or not) requests, * second parameter is the total number of images originally requested to be prefetched * @param completionBlock block to be called when prefetching is completed * first param is the number of completed (successful or not) requests, * second parameter is the number of skipped requests */ - (void)prefetchURLs:(NSArray *)urls progress:(SDWebImagePrefetcherProgressBlock)progressBlock completed:(SDWebImagePrefetcherCompletionBlock)completionBlock; /** * Remove and cancel queued list */ - (void)cancelPrefetching; @end ================================================ FILE: Example/Pods/SDWebImage/SDWebImage/SDWebImagePrefetcher.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImagePrefetcher.h" @interface SDWebImagePrefetcher () @property (strong, nonatomic) SDWebImageManager *manager; @property (strong, nonatomic) NSArray *prefetchURLs; @property (assign, nonatomic) NSUInteger requestedCount; @property (assign, nonatomic) NSUInteger skippedCount; @property (assign, nonatomic) NSUInteger finishedCount; @property (assign, nonatomic) NSTimeInterval startedTime; @property (copy, nonatomic) SDWebImagePrefetcherCompletionBlock completionBlock; @property (copy, nonatomic) SDWebImagePrefetcherProgressBlock progressBlock; @end @implementation SDWebImagePrefetcher + (SDWebImagePrefetcher *)sharedImagePrefetcher { static dispatch_once_t once; static id instance; dispatch_once(&once, ^{ instance = [self new]; }); return instance; } - (id)init { return [self initWithImageManager:[SDWebImageManager new]]; } - (id)initWithImageManager:(SDWebImageManager *)manager { if ((self = [super init])) { _manager = manager; _options = SDWebImageLowPriority; _prefetcherQueue = dispatch_get_main_queue(); self.maxConcurrentDownloads = 3; } return self; } - (void)setMaxConcurrentDownloads:(NSUInteger)maxConcurrentDownloads { self.manager.imageDownloader.maxConcurrentDownloads = maxConcurrentDownloads; } - (NSUInteger)maxConcurrentDownloads { return self.manager.imageDownloader.maxConcurrentDownloads; } - (void)startPrefetchingAtIndex:(NSUInteger)index { if (index >= self.prefetchURLs.count) return; self.requestedCount++; [self.manager downloadImageWithURL:self.prefetchURLs[index] options:self.options progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { if (!finished) return; self.finishedCount++; if (image) { if (self.progressBlock) { self.progressBlock(self.finishedCount,[self.prefetchURLs count]); } } else { if (self.progressBlock) { self.progressBlock(self.finishedCount,[self.prefetchURLs count]); } // Add last failed self.skippedCount++; } if ([self.delegate respondsToSelector:@selector(imagePrefetcher:didPrefetchURL:finishedCount:totalCount:)]) { [self.delegate imagePrefetcher:self didPrefetchURL:self.prefetchURLs[index] finishedCount:self.finishedCount totalCount:self.prefetchURLs.count ]; } if (self.prefetchURLs.count > self.requestedCount) { dispatch_async(self.prefetcherQueue, ^{ [self startPrefetchingAtIndex:self.requestedCount]; }); } else if (self.finishedCount == self.requestedCount) { [self reportStatus]; if (self.completionBlock) { self.completionBlock(self.finishedCount, self.skippedCount); self.completionBlock = nil; } self.progressBlock = nil; } }]; } - (void)reportStatus { NSUInteger total = [self.prefetchURLs count]; if ([self.delegate respondsToSelector:@selector(imagePrefetcher:didFinishWithTotalCount:skippedCount:)]) { [self.delegate imagePrefetcher:self didFinishWithTotalCount:(total - self.skippedCount) skippedCount:self.skippedCount ]; } } - (void)prefetchURLs:(NSArray *)urls { [self prefetchURLs:urls progress:nil completed:nil]; } - (void)prefetchURLs:(NSArray *)urls progress:(SDWebImagePrefetcherProgressBlock)progressBlock completed:(SDWebImagePrefetcherCompletionBlock)completionBlock { [self cancelPrefetching]; // Prevent duplicate prefetch request self.startedTime = CFAbsoluteTimeGetCurrent(); self.prefetchURLs = urls; self.completionBlock = completionBlock; self.progressBlock = progressBlock; if (urls.count == 0) { if (completionBlock) { completionBlock(0,0); } } else { // Starts prefetching from the very first image on the list with the max allowed concurrency NSUInteger listCount = self.prefetchURLs.count; for (NSUInteger i = 0; i < self.maxConcurrentDownloads && self.requestedCount < listCount; i++) { [self startPrefetchingAtIndex:i]; } } } - (void)cancelPrefetching { self.prefetchURLs = nil; self.skippedCount = 0; self.requestedCount = 0; self.finishedCount = 0; [self.manager cancelAll]; } @end ================================================ FILE: Example/Pods/SDWebImage/SDWebImage/UIButton+WebCache.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" #import "SDWebImageManager.h" /** * Integrates SDWebImage async downloading and caching of remote images with UIButtonView. */ @interface UIButton (WebCache) /** * Get the current image URL. */ - (NSURL *)sd_currentImageURL; /** * Get the image URL for a control state. * * @param state Which state you want to know the URL for. The values are described in UIControlState. */ - (NSURL *)sd_imageURLForState:(UIControlState)state; /** * Set the imageView `image` with an `url`. * * The download is asynchronous and cached. * * @param url The url for the image. * @param state The state that uses the specified title. The values are described in UIControlState. */ - (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state; /** * Set the imageView `image` with an `url` and a placeholder. * * The download is asynchronous and cached. * * @param url The url for the image. * @param state The state that uses the specified title. The values are described in UIControlState. * @param placeholder The image to be set initially, until the image request finishes. * @see sd_setImageWithURL:placeholderImage:options: */ - (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder; /** * Set the imageView `image` with an `url`, placeholder and custom options. * * The download is asynchronous and cached. * * @param url The url for the image. * @param state The state that uses the specified title. The values are described in UIControlState. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. */ - (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options; /** * Set the imageView `image` with an `url`. * * The download is asynchronous and cached. * * @param url The url for the image. * @param state The state that uses the specified title. The values are described in UIControlState. * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state completed:(SDWebImageCompletionBlock)completedBlock; /** * Set the imageView `image` with an `url`, placeholder. * * The download is asynchronous and cached. * * @param url The url for the image. * @param state The state that uses the specified title. The values are described in UIControlState. * @param placeholder The image to be set initially, until the image request finishes. * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock; /** * Set the imageView `image` with an `url`, placeholder and custom options. * * The download is asynchronous and cached. * * @param url The url for the image. * @param state The state that uses the specified title. The values are described in UIControlState. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock; /** * Set the backgroundImageView `image` with an `url`. * * The download is asynchronous and cached. * * @param url The url for the image. * @param state The state that uses the specified title. The values are described in UIControlState. */ - (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state; /** * Set the backgroundImageView `image` with an `url` and a placeholder. * * The download is asynchronous and cached. * * @param url The url for the image. * @param state The state that uses the specified title. The values are described in UIControlState. * @param placeholder The image to be set initially, until the image request finishes. * @see sd_setImageWithURL:placeholderImage:options: */ - (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder; /** * Set the backgroundImageView `image` with an `url`, placeholder and custom options. * * The download is asynchronous and cached. * * @param url The url for the image. * @param state The state that uses the specified title. The values are described in UIControlState. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. */ - (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options; /** * Set the backgroundImageView `image` with an `url`. * * The download is asynchronous and cached. * * @param url The url for the image. * @param state The state that uses the specified title. The values are described in UIControlState. * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state completed:(SDWebImageCompletionBlock)completedBlock; /** * Set the backgroundImageView `image` with an `url`, placeholder. * * The download is asynchronous and cached. * * @param url The url for the image. * @param state The state that uses the specified title. The values are described in UIControlState. * @param placeholder The image to be set initially, until the image request finishes. * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock; /** * Set the backgroundImageView `image` with an `url`, placeholder and custom options. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock; /** * Cancel the current image download */ - (void)sd_cancelImageLoadForState:(UIControlState)state; /** * Cancel the current backgroundImage download */ - (void)sd_cancelBackgroundImageLoadForState:(UIControlState)state; @end @interface UIButton (WebCacheDeprecated) - (NSURL *)currentImageURL __deprecated_msg("Use `sd_currentImageURL`"); - (NSURL *)imageURLForState:(UIControlState)state __deprecated_msg("Use `sd_imageURLForState:`"); - (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:forState:`"); - (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:forState:placeholderImage:`"); - (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:forState:placeholderImage:options:`"); - (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:forState:completed:`"); - (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:forState:placeholderImage:completed:`"); - (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:forState:placeholderImage:options:completed:`"); - (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state __deprecated_msg("Method deprecated. Use `sd_setBackgroundImageWithURL:forState:`"); - (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder __deprecated_msg("Method deprecated. Use `sd_setBackgroundImageWithURL:forState:placeholderImage:`"); - (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options __deprecated_msg("Method deprecated. Use `sd_setBackgroundImageWithURL:forState:placeholderImage:options:`"); - (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setBackgroundImageWithURL:forState:completed:`"); - (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setBackgroundImageWithURL:forState:placeholderImage:completed:`"); - (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setBackgroundImageWithURL:forState:placeholderImage:options:completed:`"); - (void)cancelCurrentImageLoad __deprecated_msg("Use `sd_cancelImageLoadForState:`"); - (void)cancelBackgroundImageLoadForState:(UIControlState)state __deprecated_msg("Use `sd_cancelBackgroundImageLoadForState:`"); @end ================================================ FILE: Example/Pods/SDWebImage/SDWebImage/UIButton+WebCache.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "UIButton+WebCache.h" #import "objc/runtime.h" #import "UIView+WebCacheOperation.h" static char imageURLStorageKey; @implementation UIButton (WebCache) - (NSURL *)sd_currentImageURL { NSURL *url = self.imageURLStorage[@(self.state)]; if (!url) { url = self.imageURLStorage[@(UIControlStateNormal)]; } return url; } - (NSURL *)sd_imageURLForState:(UIControlState)state { return self.imageURLStorage[@(state)]; } - (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state { [self sd_setImageWithURL:url forState:state placeholderImage:nil options:0 completed:nil]; } - (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder { [self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:nil]; } - (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options { [self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:options completed:nil]; } - (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state completed:(SDWebImageCompletionBlock)completedBlock { [self sd_setImageWithURL:url forState:state placeholderImage:nil options:0 completed:completedBlock]; } - (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock { [self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:completedBlock]; } - (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock { [self setImage:placeholder forState:state]; [self sd_cancelImageLoadForState:state]; if (!url) { [self.imageURLStorage removeObjectForKey:@(state)]; dispatch_main_async_safe(^{ if (completedBlock) { NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}]; completedBlock(nil, error, SDImageCacheTypeNone, url); } }); return; } self.imageURLStorage[@(state)] = url; __weak __typeof(self)wself = self; id operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { if (!wself) return; dispatch_main_sync_safe(^{ __strong UIButton *sself = wself; if (!sself) return; if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) { completedBlock(image, error, cacheType, url); return; } else if (image) { [sself setImage:image forState:state]; } if (completedBlock && finished) { completedBlock(image, error, cacheType, url); } }); }]; [self sd_setImageLoadOperation:operation forState:state]; } - (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state { [self sd_setBackgroundImageWithURL:url forState:state placeholderImage:nil options:0 completed:nil]; } - (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder { [self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:nil]; } - (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options { [self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:options completed:nil]; } - (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state completed:(SDWebImageCompletionBlock)completedBlock { [self sd_setBackgroundImageWithURL:url forState:state placeholderImage:nil options:0 completed:completedBlock]; } - (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock { [self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:completedBlock]; } - (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock { [self sd_cancelBackgroundImageLoadForState:state]; [self setBackgroundImage:placeholder forState:state]; if (url) { __weak __typeof(self)wself = self; id operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { if (!wself) return; dispatch_main_sync_safe(^{ __strong UIButton *sself = wself; if (!sself) return; if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) { completedBlock(image, error, cacheType, url); return; } else if (image) { [sself setBackgroundImage:image forState:state]; } if (completedBlock && finished) { completedBlock(image, error, cacheType, url); } }); }]; [self sd_setBackgroundImageLoadOperation:operation forState:state]; } else { dispatch_main_async_safe(^{ NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}]; if (completedBlock) { completedBlock(nil, error, SDImageCacheTypeNone, url); } }); } } - (void)sd_setImageLoadOperation:(id)operation forState:(UIControlState)state { [self sd_setImageLoadOperation:operation forKey:[NSString stringWithFormat:@"UIButtonImageOperation%@", @(state)]]; } - (void)sd_cancelImageLoadForState:(UIControlState)state { [self sd_cancelImageLoadOperationWithKey:[NSString stringWithFormat:@"UIButtonImageOperation%@", @(state)]]; } - (void)sd_setBackgroundImageLoadOperation:(id)operation forState:(UIControlState)state { [self sd_setImageLoadOperation:operation forKey:[NSString stringWithFormat:@"UIButtonBackgroundImageOperation%@", @(state)]]; } - (void)sd_cancelBackgroundImageLoadForState:(UIControlState)state { [self sd_cancelImageLoadOperationWithKey:[NSString stringWithFormat:@"UIButtonBackgroundImageOperation%@", @(state)]]; } - (NSMutableDictionary *)imageURLStorage { NSMutableDictionary *storage = objc_getAssociatedObject(self, &imageURLStorageKey); if (!storage) { storage = [NSMutableDictionary dictionary]; objc_setAssociatedObject(self, &imageURLStorageKey, storage, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } return storage; } @end @implementation UIButton (WebCacheDeprecated) - (NSURL *)currentImageURL { return [self sd_currentImageURL]; } - (NSURL *)imageURLForState:(UIControlState)state { return [self sd_imageURLForState:state]; } - (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state { [self sd_setImageWithURL:url forState:state placeholderImage:nil options:0 completed:nil]; } - (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder { [self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:nil]; } - (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options { [self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:options completed:nil]; } - (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state completed:(SDWebImageCompletedBlock)completedBlock { [self sd_setImageWithURL:url forState:state placeholderImage:nil options:0 completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { if (completedBlock) { completedBlock(image, error, cacheType); } }]; } - (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletedBlock)completedBlock { [self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { if (completedBlock) { completedBlock(image, error, cacheType); } }]; } - (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock { [self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:options completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { if (completedBlock) { completedBlock(image, error, cacheType); } }]; } - (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state { [self sd_setBackgroundImageWithURL:url forState:state placeholderImage:nil options:0 completed:nil]; } - (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder { [self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:nil]; } - (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options { [self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:options completed:nil]; } - (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state completed:(SDWebImageCompletedBlock)completedBlock { [self sd_setBackgroundImageWithURL:url forState:state placeholderImage:nil options:0 completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { if (completedBlock) { completedBlock(image, error, cacheType); } }]; } - (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletedBlock)completedBlock { [self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { if (completedBlock) { completedBlock(image, error, cacheType); } }]; } - (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock { [self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:options completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { if (completedBlock) { completedBlock(image, error, cacheType); } }]; } - (void)cancelCurrentImageLoad { // in a backwards compatible manner, cancel for current state [self sd_cancelImageLoadForState:self.state]; } - (void)cancelBackgroundImageLoadForState:(UIControlState)state { [self sd_cancelBackgroundImageLoadForState:state]; } @end ================================================ FILE: Example/Pods/SDWebImage/SDWebImage/UIImage+GIF.h ================================================ // // UIImage+GIF.h // LBGIFImage // // Created by Laurin Brandner on 06.01.12. // Copyright (c) 2012 __MyCompanyName__. All rights reserved. // #import @interface UIImage (GIF) + (UIImage *)sd_animatedGIFNamed:(NSString *)name; + (UIImage *)sd_animatedGIFWithData:(NSData *)data; - (UIImage *)sd_animatedImageByScalingAndCroppingToSize:(CGSize)size; @end ================================================ FILE: Example/Pods/SDWebImage/SDWebImage/UIImage+GIF.m ================================================ // // UIImage+GIF.m // LBGIFImage // // Created by Laurin Brandner on 06.01.12. // Copyright (c) 2012 __MyCompanyName__. All rights reserved. // #import "UIImage+GIF.h" #import @implementation UIImage (GIF) + (UIImage *)sd_animatedGIFWithData:(NSData *)data { if (!data) { return nil; } CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL); size_t count = CGImageSourceGetCount(source); UIImage *animatedImage; if (count <= 1) { animatedImage = [[UIImage alloc] initWithData:data]; } else { NSMutableArray *images = [NSMutableArray array]; NSTimeInterval duration = 0.0f; for (size_t i = 0; i < count; i++) { CGImageRef image = CGImageSourceCreateImageAtIndex(source, i, NULL); duration += [self sd_frameDurationAtIndex:i source:source]; [images addObject:[UIImage imageWithCGImage:image scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp]]; CGImageRelease(image); } if (!duration) { duration = (1.0f / 10.0f) * count; } animatedImage = [UIImage animatedImageWithImages:images duration:duration]; } CFRelease(source); return animatedImage; } + (float)sd_frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source { float frameDuration = 0.1f; CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil); NSDictionary *frameProperties = (__bridge NSDictionary *)cfFrameProperties; NSDictionary *gifProperties = frameProperties[(NSString *)kCGImagePropertyGIFDictionary]; NSNumber *delayTimeUnclampedProp = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime]; if (delayTimeUnclampedProp) { frameDuration = [delayTimeUnclampedProp floatValue]; } else { NSNumber *delayTimeProp = gifProperties[(NSString *)kCGImagePropertyGIFDelayTime]; if (delayTimeProp) { frameDuration = [delayTimeProp floatValue]; } } // Many annoying ads specify a 0 duration to make an image flash as quickly as possible. // We follow Firefox's behavior and use a duration of 100 ms for any frames that specify // a duration of <= 10 ms. See and // for more information. if (frameDuration < 0.011f) { frameDuration = 0.100f; } CFRelease(cfFrameProperties); return frameDuration; } + (UIImage *)sd_animatedGIFNamed:(NSString *)name { CGFloat scale = [UIScreen mainScreen].scale; if (scale > 1.0f) { NSString *retinaPath = [[NSBundle mainBundle] pathForResource:[name stringByAppendingString:@"@2x"] ofType:@"gif"]; NSData *data = [NSData dataWithContentsOfFile:retinaPath]; if (data) { return [UIImage sd_animatedGIFWithData:data]; } NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:@"gif"]; data = [NSData dataWithContentsOfFile:path]; if (data) { return [UIImage sd_animatedGIFWithData:data]; } return [UIImage imageNamed:name]; } else { NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:@"gif"]; NSData *data = [NSData dataWithContentsOfFile:path]; if (data) { return [UIImage sd_animatedGIFWithData:data]; } return [UIImage imageNamed:name]; } } - (UIImage *)sd_animatedImageByScalingAndCroppingToSize:(CGSize)size { if (CGSizeEqualToSize(self.size, size) || CGSizeEqualToSize(size, CGSizeZero)) { return self; } CGSize scaledSize = size; CGPoint thumbnailPoint = CGPointZero; CGFloat widthFactor = size.width / self.size.width; CGFloat heightFactor = size.height / self.size.height; CGFloat scaleFactor = (widthFactor > heightFactor) ? widthFactor : heightFactor; scaledSize.width = self.size.width * scaleFactor; scaledSize.height = self.size.height * scaleFactor; if (widthFactor > heightFactor) { thumbnailPoint.y = (size.height - scaledSize.height) * 0.5; } else if (widthFactor < heightFactor) { thumbnailPoint.x = (size.width - scaledSize.width) * 0.5; } NSMutableArray *scaledImages = [NSMutableArray array]; for (UIImage *image in self.images) { UIGraphicsBeginImageContextWithOptions(size, NO, 0.0); [image drawInRect:CGRectMake(thumbnailPoint.x, thumbnailPoint.y, scaledSize.width, scaledSize.height)]; UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); [scaledImages addObject:newImage]; UIGraphicsEndImageContext(); } return [UIImage animatedImageWithImages:scaledImages duration:self.duration]; } @end ================================================ FILE: Example/Pods/SDWebImage/SDWebImage/UIImage+MultiFormat.h ================================================ // // UIImage+MultiFormat.h // SDWebImage // // Created by Olivier Poitrey on 07/06/13. // Copyright (c) 2013 Dailymotion. All rights reserved. // #import @interface UIImage (MultiFormat) + (UIImage *)sd_imageWithData:(NSData *)data; @end ================================================ FILE: Example/Pods/SDWebImage/SDWebImage/UIImage+MultiFormat.m ================================================ // // UIImage+MultiFormat.m // SDWebImage // // Created by Olivier Poitrey on 07/06/13. // Copyright (c) 2013 Dailymotion. All rights reserved. // #import "UIImage+MultiFormat.h" #import "UIImage+GIF.h" #import "NSData+ImageContentType.h" #import #ifdef SD_WEBP #import "UIImage+WebP.h" #endif @implementation UIImage (MultiFormat) + (UIImage *)sd_imageWithData:(NSData *)data { if (!data) { return nil; } UIImage *image; NSString *imageContentType = [NSData sd_contentTypeForImageData:data]; if ([imageContentType isEqualToString:@"image/gif"]) { image = [UIImage sd_animatedGIFWithData:data]; } #ifdef SD_WEBP else if ([imageContentType isEqualToString:@"image/webp"]) { image = [UIImage sd_imageWithWebPData:data]; } #endif else { image = [[UIImage alloc] initWithData:data]; UIImageOrientation orientation = [self sd_imageOrientationFromImageData:data]; if (orientation != UIImageOrientationUp) { image = [UIImage imageWithCGImage:image.CGImage scale:image.scale orientation:orientation]; } } return image; } +(UIImageOrientation)sd_imageOrientationFromImageData:(NSData *)imageData { UIImageOrientation result = UIImageOrientationUp; CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL); if (imageSource) { CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL); if (properties) { CFTypeRef val; int exifOrientation; val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation); if (val) { CFNumberGetValue(val, kCFNumberIntType, &exifOrientation); result = [self sd_exifOrientationToiOSOrientation:exifOrientation]; } // else - if it's not set it remains at up CFRelease((CFTypeRef) properties); } else { //NSLog(@"NO PROPERTIES, FAIL"); } CFRelease(imageSource); } return result; } #pragma mark EXIF orientation tag converter // Convert an EXIF image orientation to an iOS one. // reference see here: http://sylvana.net/jpegcrop/exif_orientation.html + (UIImageOrientation) sd_exifOrientationToiOSOrientation:(int)exifOrientation { UIImageOrientation orientation = UIImageOrientationUp; switch (exifOrientation) { case 1: orientation = UIImageOrientationUp; break; case 3: orientation = UIImageOrientationDown; break; case 8: orientation = UIImageOrientationLeft; break; case 6: orientation = UIImageOrientationRight; break; case 2: orientation = UIImageOrientationUpMirrored; break; case 4: orientation = UIImageOrientationDownMirrored; break; case 5: orientation = UIImageOrientationLeftMirrored; break; case 7: orientation = UIImageOrientationRightMirrored; break; default: break; } return orientation; } @end ================================================ FILE: Example/Pods/SDWebImage/SDWebImage/UIImageView+HighlightedWebCache.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDWebImageCompat.h" #import "SDWebImageManager.h" /** * Integrates SDWebImage async downloading and caching of remote images with UIImageView for highlighted state. */ @interface UIImageView (HighlightedWebCache) /** * Set the imageView `highlightedImage` with an `url`. * * The download is asynchronous and cached. * * @param url The url for the image. */ - (void)sd_setHighlightedImageWithURL:(NSURL *)url; /** * Set the imageView `highlightedImage` with an `url` and custom options. * * The download is asynchronous and cached. * * @param url The url for the image. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. */ - (void)sd_setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options; /** * Set the imageView `highlightedImage` with an `url`. * * The download is asynchronous and cached. * * @param url The url for the image. * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setHighlightedImageWithURL:(NSURL *)url completed:(SDWebImageCompletionBlock)completedBlock; /** * Set the imageView `highlightedImage` with an `url` and custom options. * * The download is asynchronous and cached. * * @param url The url for the image. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock; /** * Set the imageView `highlightedImage` with an `url` and custom options. * * The download is asynchronous and cached. * * @param url The url for the image. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param progressBlock A block called while image is downloading * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock; /** * Cancel the current download */ - (void)sd_cancelCurrentHighlightedImageLoad; @end @interface UIImageView (HighlightedWebCacheDeprecated) - (void)setHighlightedImageWithURL:(NSURL *)url __deprecated_msg("Method deprecated. Use `sd_setHighlightedImageWithURL:`"); - (void)setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options __deprecated_msg("Method deprecated. Use `sd_setHighlightedImageWithURL:options:`"); - (void)setHighlightedImageWithURL:(NSURL *)url completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setHighlightedImageWithURL:completed:`"); - (void)setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setHighlightedImageWithURL:options:completed:`"); - (void)setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setHighlightedImageWithURL:options:progress:completed:`"); - (void)cancelCurrentHighlightedImageLoad __deprecated_msg("Use `sd_cancelCurrentHighlightedImageLoad`"); @end ================================================ FILE: Example/Pods/SDWebImage/SDWebImage/UIImageView+HighlightedWebCache.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "UIImageView+HighlightedWebCache.h" #import "UIView+WebCacheOperation.h" #define UIImageViewHighlightedWebCacheOperationKey @"highlightedImage" @implementation UIImageView (HighlightedWebCache) - (void)sd_setHighlightedImageWithURL:(NSURL *)url { [self sd_setHighlightedImageWithURL:url options:0 progress:nil completed:nil]; } - (void)sd_setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options { [self sd_setHighlightedImageWithURL:url options:options progress:nil completed:nil]; } - (void)sd_setHighlightedImageWithURL:(NSURL *)url completed:(SDWebImageCompletionBlock)completedBlock { [self sd_setHighlightedImageWithURL:url options:0 progress:nil completed:completedBlock]; } - (void)sd_setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock { [self sd_setHighlightedImageWithURL:url options:options progress:nil completed:completedBlock]; } - (void)sd_setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock { [self sd_cancelCurrentHighlightedImageLoad]; if (url) { __weak __typeof(self)wself = self; id operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { if (!wself) return; dispatch_main_sync_safe (^ { if (!wself) return; if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) { completedBlock(image, error, cacheType, url); return; } else if (image) { wself.highlightedImage = image; [wself setNeedsLayout]; } if (completedBlock && finished) { completedBlock(image, error, cacheType, url); } }); }]; [self sd_setImageLoadOperation:operation forKey:UIImageViewHighlightedWebCacheOperationKey]; } else { dispatch_main_async_safe(^{ NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}]; if (completedBlock) { completedBlock(nil, error, SDImageCacheTypeNone, url); } }); } } - (void)sd_cancelCurrentHighlightedImageLoad { [self sd_cancelImageLoadOperationWithKey:UIImageViewHighlightedWebCacheOperationKey]; } @end @implementation UIImageView (HighlightedWebCacheDeprecated) - (void)setHighlightedImageWithURL:(NSURL *)url { [self sd_setHighlightedImageWithURL:url options:0 progress:nil completed:nil]; } - (void)setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options { [self sd_setHighlightedImageWithURL:url options:options progress:nil completed:nil]; } - (void)setHighlightedImageWithURL:(NSURL *)url completed:(SDWebImageCompletedBlock)completedBlock { [self sd_setHighlightedImageWithURL:url options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { if (completedBlock) { completedBlock(image, error, cacheType); } }]; } - (void)setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock { [self sd_setHighlightedImageWithURL:url options:options progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { if (completedBlock) { completedBlock(image, error, cacheType); } }]; } - (void)setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletedBlock)completedBlock { [self sd_setHighlightedImageWithURL:url options:0 progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { if (completedBlock) { completedBlock(image, error, cacheType); } }]; } - (void)cancelCurrentHighlightedImageLoad { [self sd_cancelCurrentHighlightedImageLoad]; } @end ================================================ FILE: Example/Pods/SDWebImage/SDWebImage/UIImageView+WebCache.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImageCompat.h" #import "SDWebImageManager.h" /** * Integrates SDWebImage async downloading and caching of remote images with UIImageView. * * Usage with a UITableViewCell sub-class: * * @code #import ... - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *MyIdentifier = @"MyIdentifier"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier] autorelease]; } // Here we use the provided sd_setImageWithURL: method to load the web image // Ensure you use a placeholder image otherwise cells will be initialized with no image [cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://example.com/image.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder"]]; cell.textLabel.text = @"My Text"; return cell; } * @endcode */ @interface UIImageView (WebCache) /** * Get the current image URL. * * Note that because of the limitations of categories this property can get out of sync * if you use sd_setImage: directly. */ - (NSURL *)sd_imageURL; /** * Set the imageView `image` with an `url`. * * The download is asynchronous and cached. * * @param url The url for the image. */ - (void)sd_setImageWithURL:(NSURL *)url; /** * Set the imageView `image` with an `url` and a placeholder. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @see sd_setImageWithURL:placeholderImage:options: */ - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder; /** * Set the imageView `image` with an `url`, placeholder and custom options. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. */ - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options; /** * Set the imageView `image` with an `url`. * * The download is asynchronous and cached. * * @param url The url for the image. * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setImageWithURL:(NSURL *)url completed:(SDWebImageCompletionBlock)completedBlock; /** * Set the imageView `image` with an `url`, placeholder. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock; /** * Set the imageView `image` with an `url`, placeholder and custom options. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock; /** * Set the imageView `image` with an `url`, placeholder and custom options. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param progressBlock A block called while image is downloading * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock; /** * Set the imageView `image` with an `url` and optionally a placeholder image. * * The download is asynchronous and cached. * * @param url The url for the image. * @param placeholder The image to be set initially, until the image request finishes. * @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values. * @param progressBlock A block called while image is downloading * @param completedBlock A block called when operation has been completed. This block has no return value * and takes the requested UIImage as first parameter. In case of error the image parameter * is nil and the second parameter may contain an NSError. The third parameter is a Boolean * indicating if the image was retrieved from the local cache or from the network. * The fourth parameter is the original image url. */ - (void)sd_setImageWithPreviousCachedImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock; /** * Download an array of images and starts them in an animation loop * * @param arrayOfURLs An array of NSURL */ - (void)sd_setAnimationImagesWithURLs:(NSArray *)arrayOfURLs; /** * Cancel the current download */ - (void)sd_cancelCurrentImageLoad; - (void)sd_cancelCurrentAnimationImagesLoad; /** * Show activity UIActivityIndicatorView */ - (void)setShowActivityIndicatorView:(BOOL)show; /** * set desired UIActivityIndicatorViewStyle * * @param style The style of the UIActivityIndicatorView */ - (void)setIndicatorStyle:(UIActivityIndicatorViewStyle)style; @end @interface UIImageView (WebCacheDeprecated) - (NSURL *)imageURL __deprecated_msg("Use `sd_imageURL`"); - (void)setImageWithURL:(NSURL *)url __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:`"); - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:placeholderImage:`"); - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:placeholderImage:options`"); - (void)setImageWithURL:(NSURL *)url completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:completed:`"); - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:placeholderImage:completed:`"); - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:placeholderImage:options:completed:`"); - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:placeholderImage:options:progress:completed:`"); - (void)setAnimationImagesWithURLs:(NSArray *)arrayOfURLs __deprecated_msg("Use `sd_setAnimationImagesWithURLs:`"); - (void)cancelCurrentArrayLoad __deprecated_msg("Use `sd_cancelCurrentAnimationImagesLoad`"); - (void)cancelCurrentImageLoad __deprecated_msg("Use `sd_cancelCurrentImageLoad`"); @end ================================================ FILE: Example/Pods/SDWebImage/SDWebImage/UIImageView+WebCache.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "UIImageView+WebCache.h" #import "objc/runtime.h" #import "UIView+WebCacheOperation.h" static char imageURLKey; static char TAG_ACTIVITY_INDICATOR; static char TAG_ACTIVITY_STYLE; static char TAG_ACTIVITY_SHOW; @implementation UIImageView (WebCache) - (void)sd_setImageWithURL:(NSURL *)url { [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil]; } - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder { [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil]; } - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options { [self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil]; } - (void)sd_setImageWithURL:(NSURL *)url completed:(SDWebImageCompletionBlock)completedBlock { [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:completedBlock]; } - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock { [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:completedBlock]; } - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock { [self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:completedBlock]; } - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock { [self sd_cancelCurrentImageLoad]; objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC); if (!(options & SDWebImageDelayPlaceholder)) { dispatch_main_async_safe(^{ self.image = placeholder; }); } if (url) { // check if activityView is enabled or not if ([self showActivityIndicatorView]) { [self addActivityIndicator]; } __weak __typeof(self)wself = self; id operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { [wself removeActivityIndicator]; if (!wself) return; dispatch_main_sync_safe(^{ if (!wself) return; if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) { completedBlock(image, error, cacheType, url); return; } else if (image) { wself.image = image; [wself setNeedsLayout]; } else { if ((options & SDWebImageDelayPlaceholder)) { wself.image = placeholder; [wself setNeedsLayout]; } } if (completedBlock && finished) { completedBlock(image, error, cacheType, url); } }); }]; [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"]; } else { dispatch_main_async_safe(^{ [self removeActivityIndicator]; if (completedBlock) { NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}]; completedBlock(nil, error, SDImageCacheTypeNone, url); } }); } } - (void)sd_setImageWithPreviousCachedImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock { NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:url]; UIImage *lastPreviousCachedImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:key]; [self sd_setImageWithURL:url placeholderImage:lastPreviousCachedImage ?: placeholder options:options progress:progressBlock completed:completedBlock]; } - (NSURL *)sd_imageURL { return objc_getAssociatedObject(self, &imageURLKey); } - (void)sd_setAnimationImagesWithURLs:(NSArray *)arrayOfURLs { [self sd_cancelCurrentAnimationImagesLoad]; __weak __typeof(self)wself = self; NSMutableArray *operationsArray = [[NSMutableArray alloc] init]; for (NSURL *logoImageURL in arrayOfURLs) { id operation = [SDWebImageManager.sharedManager downloadImageWithURL:logoImageURL options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { if (!wself) return; dispatch_main_sync_safe(^{ __strong UIImageView *sself = wself; [sself stopAnimating]; if (sself && image) { NSMutableArray *currentImages = [[sself animationImages] mutableCopy]; if (!currentImages) { currentImages = [[NSMutableArray alloc] init]; } [currentImages addObject:image]; sself.animationImages = currentImages; [sself setNeedsLayout]; } [sself startAnimating]; }); }]; [operationsArray addObject:operation]; } [self sd_setImageLoadOperation:[NSArray arrayWithArray:operationsArray] forKey:@"UIImageViewAnimationImages"]; } - (void)sd_cancelCurrentImageLoad { [self sd_cancelImageLoadOperationWithKey:@"UIImageViewImageLoad"]; } - (void)sd_cancelCurrentAnimationImagesLoad { [self sd_cancelImageLoadOperationWithKey:@"UIImageViewAnimationImages"]; } #pragma mark - - (UIActivityIndicatorView *)activityIndicator { return (UIActivityIndicatorView *)objc_getAssociatedObject(self, &TAG_ACTIVITY_INDICATOR); } - (void)setActivityIndicator:(UIActivityIndicatorView *)activityIndicator { objc_setAssociatedObject(self, &TAG_ACTIVITY_INDICATOR, activityIndicator, OBJC_ASSOCIATION_RETAIN); } - (void)setShowActivityIndicatorView:(BOOL)show{ objc_setAssociatedObject(self, &TAG_ACTIVITY_SHOW, [NSNumber numberWithBool:show], OBJC_ASSOCIATION_RETAIN); } - (BOOL)showActivityIndicatorView{ return [objc_getAssociatedObject(self, &TAG_ACTIVITY_SHOW) boolValue]; } - (void)setIndicatorStyle:(UIActivityIndicatorViewStyle)style{ objc_setAssociatedObject(self, &TAG_ACTIVITY_STYLE, [NSNumber numberWithInt:style], OBJC_ASSOCIATION_RETAIN); } - (int)getIndicatorStyle{ return [objc_getAssociatedObject(self, &TAG_ACTIVITY_STYLE) intValue]; } - (void)addActivityIndicator { if (!self.activityIndicator) { self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:[self getIndicatorStyle]]; self.activityIndicator.translatesAutoresizingMaskIntoConstraints = NO; dispatch_main_async_safe(^{ [self addSubview:self.activityIndicator]; [self addConstraint:[NSLayoutConstraint constraintWithItem:self.activityIndicator attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]]; [self addConstraint:[NSLayoutConstraint constraintWithItem:self.activityIndicator attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]]; }); } dispatch_main_async_safe(^{ [self.activityIndicator startAnimating]; }); } - (void)removeActivityIndicator { if (self.activityIndicator) { [self.activityIndicator removeFromSuperview]; self.activityIndicator = nil; } } @end @implementation UIImageView (WebCacheDeprecated) - (NSURL *)imageURL { return [self sd_imageURL]; } - (void)setImageWithURL:(NSURL *)url { [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil]; } - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder { [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil]; } - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options { [self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil]; } - (void)setImageWithURL:(NSURL *)url completed:(SDWebImageCompletedBlock)completedBlock { [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { if (completedBlock) { completedBlock(image, error, cacheType); } }]; } - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletedBlock)completedBlock { [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { if (completedBlock) { completedBlock(image, error, cacheType); } }]; } - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock { [self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { if (completedBlock) { completedBlock(image, error, cacheType); } }]; } - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletedBlock)completedBlock { [self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { if (completedBlock) { completedBlock(image, error, cacheType); } }]; } - (void)cancelCurrentArrayLoad { [self sd_cancelCurrentAnimationImagesLoad]; } - (void)cancelCurrentImageLoad { [self sd_cancelCurrentImageLoad]; } - (void)setAnimationImagesWithURLs:(NSArray *)arrayOfURLs { [self sd_setAnimationImagesWithURLs:arrayOfURLs]; } @end ================================================ FILE: Example/Pods/SDWebImage/SDWebImage/UIView+WebCacheOperation.h ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import #import "SDWebImageManager.h" @interface UIView (WebCacheOperation) /** * Set the image load operation (storage in a UIView based dictionary) * * @param operation the operation * @param key key for storing the operation */ - (void)sd_setImageLoadOperation:(id)operation forKey:(NSString *)key; /** * Cancel all operations for the current UIView and key * * @param key key for identifying the operations */ - (void)sd_cancelImageLoadOperationWithKey:(NSString *)key; /** * Just remove the operations corresponding to the current UIView and key without cancelling them * * @param key key for identifying the operations */ - (void)sd_removeImageLoadOperationWithKey:(NSString *)key; @end ================================================ FILE: Example/Pods/SDWebImage/SDWebImage/UIView+WebCacheOperation.m ================================================ /* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "UIView+WebCacheOperation.h" #import "objc/runtime.h" static char loadOperationKey; @implementation UIView (WebCacheOperation) - (NSMutableDictionary *)operationDictionary { NSMutableDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey); if (operations) { return operations; } operations = [NSMutableDictionary dictionary]; objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC); return operations; } - (void)sd_setImageLoadOperation:(id)operation forKey:(NSString *)key { [self sd_cancelImageLoadOperationWithKey:key]; NSMutableDictionary *operationDictionary = [self operationDictionary]; [operationDictionary setObject:operation forKey:key]; } - (void)sd_cancelImageLoadOperationWithKey:(NSString *)key { // Cancel in progress downloader from queue NSMutableDictionary *operationDictionary = [self operationDictionary]; id operations = [operationDictionary objectForKey:key]; if (operations) { if ([operations isKindOfClass:[NSArray class]]) { for (id operation in operations) { if (operation) { [operation cancel]; } } } else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){ [(id) operations cancel]; } [operationDictionary removeObjectForKey:key]; } } - (void)sd_removeImageLoadOperationWithKey:(NSString *)key { NSMutableDictionary *operationDictionary = [self operationDictionary]; [operationDictionary removeObjectForKey:key]; } @end ================================================ FILE: Example/Pods/Specta/LICENSE ================================================ Copyright (c) 2012-2014 Specta Team. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Example/Pods/Specta/README.md ================================================ # Specta A light-weight TDD / BDD framework for Objective-C. ### Status [![Build Status](https://travis-ci.org/specta/specta.png)](https://travis-ci.org/specta/specta) [![Coverage Status](https://coveralls.io/repos/specta/specta/badge.svg)](https://coveralls.io/r/specta/specta) ## FEATURES * An Objective-C RSpec-like BDD DSL * Quick and easy set up * Built on top of XCTest * Excellent Xcode integration ## SCREENSHOT ![Specta Screenshot](https://raw.githubusercontent.com/specta/specta/master/misc/specta_screenshot.jpg) ## SETUP Use [CocoaPods](http://github.com/CocoaPods/CocoaPods), [Carthage](https://github.com/carthage/carthage) or [Set up manually](#setting-up-manually) ### CocoaPods 1. Add Specta to your project's `Podfile`: ```ruby target :MyApp do # your app dependencies end target :MyAppTests do pod 'Specta', '~> 0.5' # pod 'Expecta', '~> 0.3' # expecta matchers # pod 'OCMock', '~> 2.2' # OCMock # pod 'OCHamcrest', '~> 3.0' # hamcrest matchers # pod 'OCMockito', '~> 1.0' # OCMock # pod 'LRMocky', '~> 0.9' # LRMocky end ``` 2. Run `pod update` or `pod install` in your project directory. ### Carthage 1. Add Specta to your project's `Cartfile.private` ``` github "specta/specta" ~> 0.5 ``` 2. Run `carthage update` in your project directory 3. Drag the appropriate `Specta.framework` for your platform (located in Carthage/Build/) into your application’s Xcode project, and add it to your test target(s). 4. If you are building for iOS, a new `Run Script Phase` must be added to copy the framework. The instructions can be found on [Carthage's getting started instructions](https://github.com/carthage/carthage#getting-started) ### SETTING UP MANUALLY 1. Clone from Github. 2. Run `rake` in project root to build. 3. Add a "Cocoa/Cocoa Touch Unit Testing Bundle" target if you don't already have one. 4. Copy and add all header files in `Products` folder to the Test target in your Xcode project. 5. For **OS X projects**, copy and add `Specta.framework` in `Products/osx` folder to the test target in your Xcode project. For **iOS projects**, copy and add `Specta.framework` in `Products/ios` folder to the test target in your Xcode project. You can alternatively use `libSpecta.a`, if you prefer to add it as a static library for your project. (iOS 7 and below require this) 6. Add `-ObjC` and `-all_load` to the "Other Linker Flags" build setting for the test target in your Xcode project. 7. If you encounter linking issues with `_llvm_*` symbols, ensure your target's "Generate Test Coverage Files" and "Instrument Program Flow" build settings are set to `Yes`. ## EXAMPLE ```objective-c #import // #import "Specta.h" if you're using libSpecta.a SharedExamplesBegin(MySharedExamples) // Global shared examples are shared across all spec files. sharedExamplesFor(@"foo", ^(NSDictionary *data) { __block id bar = nil; beforeEach(^{ bar = data[@"bar"]; }); it(@"should not be nil", ^{ XCTAssertNotNil(bar); }); }); SharedExamplesEnd SpecBegin(Thing) describe(@"Thing", ^{ sharedExamplesFor(@"another shared behavior", ^(NSDictionary *data) { // Locally defined shared examples can override global shared examples within its scope. }); beforeAll(^{ // This is run once and only once before all of the examples // in this group and before any beforeEach blocks. }); beforeEach(^{ // This is run before each example. }); it(@"should do stuff", ^{ // This is an example block. Place your assertions here. }); it(@"should do some stuff asynchronously", ^{ waitUntil(^(DoneCallback done) { // Async example blocks need to invoke done() callback. done(); }); }); itShouldBehaveLike(@"a shared behavior", @{@"key" : @"obj"}); itShouldBehaveLike(@"another shared behavior", ^{ // Use a block that returns a dictionary if you need the context to be evaluated lazily, // e.g. to use an object prepared in a beforeEach block. return @{@"key" : @"obj"}; }); describe(@"Nested examples", ^{ it(@"should do even more stuff", ^{ // ... }); }); pending(@"pending example"); pending(@"another pending example", ^{ // ... }); afterEach(^{ // This is run after each example. }); afterAll(^{ // This is run once and only once after all of the examples // in this group and after any afterEach blocks. }); }); SpecEnd ``` * `beforeEach` and `afterEach` are also aliased as `before` and `after` respectively. * `describe` is also aliased as `context`. * `it` is also aliased as `example` and `specify`. * `itShouldBehaveLike` is also aliased as `itBehavesLike`. * Use `pending` or prepend `x` to `describe`, `context`, `example`, `it`, and `specify` to mark examples or groups as pending. * Use `^(DoneCallback done)` as shown in the example above to make examples wait for completion. `done()` callback needs to be invoked to let Specta know that your test is complete. The default timeout is 10.0 seconds but this can be changed by calling the function `setAsyncSpecTimeout(NSTimeInterval timeout)`. * `(before|after)(Each/All)` also accept `^(DoneCallback done)`s. * Do `#define SPT_CEDAR_SYNTAX` before importing Specta if you prefer to write `SPEC_BEGIN` and `SPEC_END` instead of `SpecBegin` and `SpecEnd`. * Prepend `f` to your `describe`, `context`, `example`, `it`, and `specify` to set focus on examples or groups. When specs are focused, all unfocused specs are skipped. * To use original XCTest reporter, set an environment variable named `SPECTA_REPORTER_CLASS` to `SPTXCTestReporter` in your test scheme. * Set an environment variable `SPECTA_NO_SHUFFLE` with value `1` to disable test shuffling. * Set an environment variable `SPECTA_SEED` to specify the random seed for test shuffling. Standard XCTest matchers such as `XCTAssertEqualObjects` and `XCTAssertNil` work, but you probably want to add a nicer matcher framework - [Expecta](http://github.com/specta/expecta/) to your setup. Or if you really prefer, [OCHamcrest](https://github.com/jonreid/OCHamcrest) works fine too. Also, add a mocking framework: [OCMock](http://ocmock.org/). ## RUNNING TESTS IN COMMAND LINE * Run `rake test` in the cloned folder. ## CONTRIBUTION GUIDELINES * Please use only spaces and indent 2 spaces at a time. * Please prefix instance variable names with a single underscore (`_`). * Please prefix custom classes and functions defined in the global scope with `SPT`. ## LICENSE Copyright (c) 2012-2015 [Specta Team](https://github.com/specta?tab=members). This software is licensed under the [MIT License](http://github.com/specta/specta/raw/master/LICENSE). ================================================ FILE: Example/Pods/Specta/Specta/Specta/SPTCallSite.h ================================================ #import @interface SPTCallSite : NSObject @property (nonatomic, copy, readonly) NSString *file; @property (nonatomic, readonly) NSUInteger line; + (instancetype)callSiteWithFile:(NSString *)file line:(NSUInteger)line; - (instancetype)initWithFile:(NSString *)file line:(NSUInteger)line; @end ================================================ FILE: Example/Pods/Specta/Specta/Specta/SPTCallSite.m ================================================ #import "SPTCallSite.h" @implementation SPTCallSite + (instancetype)callSiteWithFile:(NSString *)file line:(NSUInteger)line { return [[self alloc] initWithFile:file line:line]; } - (instancetype)initWithFile:(NSString *)file line:(NSUInteger)line { self = [super init]; if (self) { _file = file; _line = line; } return self; } @end ================================================ FILE: Example/Pods/Specta/Specta/Specta/SPTCompiledExample.h ================================================ #import #import "SpectaTypes.h" @interface SPTCompiledExample : NSObject @property (nonatomic, copy) NSString *name; @property (nonatomic, copy) NSString *testCaseName; @property (nonatomic, copy) SPTSpecBlock block; @property (nonatomic) BOOL pending; @property (nonatomic, getter=isFocused) BOOL focused; @property (nonatomic) SEL testMethodSelector; - (id)initWithName:(NSString *)name testCaseName:(NSString *)testCaseName block:(SPTSpecBlock)block pending:(BOOL)pending focused:(BOOL)focused; @end ================================================ FILE: Example/Pods/Specta/Specta/Specta/SPTCompiledExample.m ================================================ #import "SPTCompiledExample.h" @implementation SPTCompiledExample - (id)initWithName:(NSString *)name testCaseName:(NSString *)testCaseName block:(SPTSpecBlock)block pending:(BOOL)pending focused:(BOOL)focused { self = [super init]; if (self) { self.name = name; self.testCaseName = testCaseName; self.block = block; self.pending = pending; self.focused = focused; } return self; } @end ================================================ FILE: Example/Pods/Specta/Specta/Specta/SPTExample.h ================================================ #import #import "SpectaTypes.h" @class SPTCallSite; @interface SPTExample : NSObject @property (nonatomic, copy) NSString *name; @property (nonatomic, retain) SPTCallSite *callSite; @property (nonatomic, copy) SPTVoidBlock block; @property (nonatomic) BOOL pending; @property (nonatomic, getter=isFocused) BOOL focused; - (id)initWithName:(NSString *)name callSite:(SPTCallSite *)callSite focused:(BOOL)focused block:(SPTVoidBlock)block; @end ================================================ FILE: Example/Pods/Specta/Specta/Specta/SPTExample.m ================================================ #import "SPTExample.h" @implementation SPTExample - (id)initWithName:(NSString *)name callSite:(SPTCallSite *)callSite focused:(BOOL)focused block:(SPTVoidBlock)block { self = [super init]; if (self) { self.name = name; self.callSite = callSite; self.block = block; self.focused = focused; self.pending = block == nil; } return self; } @end ================================================ FILE: Example/Pods/Specta/Specta/Specta/SPTExampleGroup.h ================================================ #import #import #import "SpectaTypes.h" @class SPTExample; @class SPTCallSite; @interface SPTExampleGroup : NSObject @property (nonatomic, copy) NSString *name; @property (nonatomic, strong) SPTExampleGroup *root; @property (nonatomic, strong) SPTExampleGroup *parent; @property (nonatomic, strong) NSMutableArray *children; @property (nonatomic, strong) NSMutableArray *beforeAllArray; @property (nonatomic, strong) NSMutableArray *afterAllArray; @property (nonatomic, strong) NSMutableArray *beforeEachArray; @property (nonatomic, strong) NSMutableArray *afterEachArray; @property (nonatomic, strong) NSMutableDictionary *sharedExamples; @property (nonatomic) unsigned int exampleCount; @property (nonatomic) unsigned int ranExampleCount; @property (nonatomic) unsigned int pendingExampleCount; @property (nonatomic, getter=isFocused) BOOL focused; - (id)initWithName:(NSString *)name parent:(SPTExampleGroup *)parent root:(SPTExampleGroup *)root; - (SPTExampleGroup *)addExampleGroupWithName:(NSString *)name focused:(BOOL)focused; - (SPTExample *)addExampleWithName:(NSString *)name callSite:(SPTCallSite *)callSite focused:(BOOL)focused block:(SPTVoidBlock)block; - (void)addBeforeAllBlock:(SPTVoidBlock)block; - (void)addAfterAllBlock:(SPTVoidBlock)block; - (void)addBeforeEachBlock:(SPTVoidBlock)block; - (void)addAfterEachBlock:(SPTVoidBlock)block; - (NSArray *)compileExamplesWithStack:(NSArray *)stack; @end ================================================ FILE: Example/Pods/Specta/Specta/Specta/SPTExampleGroup.m ================================================ #import "SPTExampleGroup.h" #import "SPTExample.h" #import "SPTCompiledExample.h" #import "SPTSpec.h" #import "SpectaUtility.h" #import "XCTest+Private.h" #import "SPTGlobalBeforeAfterEach.h" #import #import static NSArray *ClassesWithClassMethod(SEL classMethodSelector) { NSMutableArray *classesWithClassMethod = [[NSMutableArray alloc] init]; int numberOfClasses = objc_getClassList(NULL, 0); if (numberOfClasses > 0) { Class *classes = (Class *)malloc(sizeof(Class) *numberOfClasses); numberOfClasses = objc_getClassList(classes, numberOfClasses); for(int classIndex = 0; classIndex < numberOfClasses; classIndex++) { Class aClass = classes[classIndex]; if (class_conformsToProtocol(aClass, @protocol(SPTGlobalBeforeAfterEach))) { Method globalMethod = class_getClassMethod(aClass, classMethodSelector); if (globalMethod) { [classesWithClassMethod addObject:aClass]; } } } free(classes); } return classesWithClassMethod; } static void runExampleBlock(void (^block)(), NSString *name) { if (!SPTIsBlock(block)) { return; } ((SPTVoidBlock)block)(); } typedef NS_ENUM(NSInteger, SPTExampleGroupOrder) { SPTExampleGroupOrderOutermostFirst = 1, SPTExampleGroupOrderInnermostFirst }; @interface SPTExampleGroup () - (void)incrementExampleCount; - (void)incrementPendingExampleCount; - (void)resetRanExampleCountIfNeeded; - (void)incrementRanExampleCount; - (void)runBeforeHooks:(NSString *)compiledName; - (void)runBeforeAllHooks:(NSString *)compiledName; - (void)runBeforeEachHooks:(NSString *)compiledName; - (void)runAfterHooks:(NSString *)compiledName; - (void)runAfterEachHooks:(NSString *)compiledName; - (void)runAfterAllHooks:(NSString *)compiledName; @end @implementation SPTExampleGroup - (id)init { self = [super init]; if (self) { self.name = nil; self.root = nil; self.parent = nil; self.children = [NSMutableArray array]; self.beforeAllArray = [NSMutableArray array]; self.afterAllArray = [NSMutableArray array]; self.beforeEachArray = [NSMutableArray array]; self.afterEachArray = [NSMutableArray array]; self.sharedExamples = [NSMutableDictionary dictionary]; self.exampleCount = 0; self.pendingExampleCount = 0; self.ranExampleCount = 0; } return self; } - (id)initWithName:(NSString *)name parent:(SPTExampleGroup *)parent root:(SPTExampleGroup *)root { self = [self init]; if (self) { self.name = name; self.parent = parent; self.root = root; } return self; } - (SPTExampleGroup *)addExampleGroupWithName:(NSString *)name focused:(BOOL)focused { SPTExampleGroup *group = [[SPTExampleGroup alloc] initWithName:name parent:self root:self.root]; group.focused = focused; [self.children addObject:group]; return group; } - (SPTExample *)addExampleWithName:(NSString *)name callSite:(SPTCallSite *)callSite focused:(BOOL)focused block:(SPTVoidBlock)block { SPTExample *example; @synchronized(self) { example = [[SPTExample alloc] initWithName:name callSite:callSite focused:focused block:block]; [self.children addObject:example]; [self incrementExampleCount]; if (example.pending) { [self incrementPendingExampleCount]; } } return example; } - (void)incrementExampleCount { SPTExampleGroup *group = self; while (group != nil) { group.exampleCount ++; group = group.parent; } } - (void)incrementPendingExampleCount { SPTExampleGroup *group = self; while (group != nil) { group.pendingExampleCount ++; group = group.parent; } } - (void)resetRanExampleCountIfNeeded { SPTExampleGroup *group = self; while (group != nil) { if (group.ranExampleCount >= group.exampleCount) { group.ranExampleCount = 0; } group = group.parent; } } - (void)incrementRanExampleCount { SPTExampleGroup *group = self; while (group != nil) { group.ranExampleCount ++; group = group.parent; } } - (void)addBeforeAllBlock:(SPTVoidBlock)block { if (!block) return; [self.beforeAllArray addObject:[block copy]]; } - (void)addAfterAllBlock:(SPTVoidBlock)block { if (!block) return; [self.afterAllArray addObject:[block copy]]; } - (void)addBeforeEachBlock:(SPTVoidBlock)block { if (!block) return; [self.beforeEachArray addObject:[block copy]]; } - (void)addAfterEachBlock:(SPTVoidBlock)block { if (!block) return; [self.afterEachArray addObject:[block copy]]; } - (void)runGlobalBeforeEachHooks:(NSString *)compiledName { static NSArray *globalBeforeEachClasses; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ globalBeforeEachClasses = ClassesWithClassMethod(@selector(beforeEach)); }); for (Class class in globalBeforeEachClasses) { [class beforeEach]; } } - (void)runGlobalAfterEachHooks:(NSString *)compiledName { static NSArray *globalAfterEachClasses; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ globalAfterEachClasses = ClassesWithClassMethod(@selector(afterEach)); }); for (Class class in globalAfterEachClasses) { [class afterEach]; } } // Builds an array of example groups that enclose the current group. // When in innermost-first order, the most deeply nested example groups, // beginning with self, are placed at the beginning of the array. // When in outermost-first order, the opposite is true, and the last // group in the array (self) is the most deeply nested. - (NSArray *)exampleGroupStackInOrder:(SPTExampleGroupOrder)order { NSMutableArray *groups = [NSMutableArray array]; SPTExampleGroup *group = self; while (group != nil) { switch (order) { case SPTExampleGroupOrderOutermostFirst: [groups insertObject:group atIndex:0]; break; case SPTExampleGroupOrderInnermostFirst: [groups addObject:group]; break; } group = group.parent; } return [groups copy]; } - (void)runBeforeHooks:(NSString *)compiledName { [self runBeforeAllHooks:compiledName]; [self runBeforeEachHooks:compiledName]; } - (void)runBeforeAllHooks:(NSString *)compiledName { for(SPTExampleGroup *group in [self exampleGroupStackInOrder:SPTExampleGroupOrderOutermostFirst]) { if (group.ranExampleCount == 0) { for (id beforeAllBlock in group.beforeAllArray) { runExampleBlock(beforeAllBlock, [NSString stringWithFormat:@"%@ - before all block", compiledName]); } } } } - (void)runBeforeEachHooks:(NSString *)compiledName { [self runGlobalBeforeEachHooks:compiledName]; for (SPTExampleGroup *group in [self exampleGroupStackInOrder:SPTExampleGroupOrderOutermostFirst]) { for (id beforeEachBlock in group.beforeEachArray) { runExampleBlock(beforeEachBlock, [NSString stringWithFormat:@"%@ - before each block", compiledName]); } } } - (void)runAfterHooks:(NSString *)compiledName { [self runAfterEachHooks:compiledName]; [self runAfterAllHooks:compiledName]; } - (void)runAfterEachHooks:(NSString *)compiledName { for (SPTExampleGroup *group in [self exampleGroupStackInOrder:SPTExampleGroupOrderInnermostFirst]) { for (id afterEachBlock in group.afterEachArray) { runExampleBlock(afterEachBlock, [NSString stringWithFormat:@"%@ - after each block", compiledName]); } } [self runGlobalAfterEachHooks:compiledName]; } - (void)runAfterAllHooks:(NSString *)compiledName { for (SPTExampleGroup *group in [self exampleGroupStackInOrder:SPTExampleGroupOrderInnermostFirst]) { if (group.ranExampleCount == group.exampleCount) { for (id afterAllBlock in group.afterAllArray) { runExampleBlock(afterAllBlock, [NSString stringWithFormat:@"%@ - after all block", compiledName]); } } } } - (BOOL)isFocusedOrHasFocusedAncestor { SPTExampleGroup *ancestor = self; while (ancestor != nil) { if (ancestor.focused) { return YES; } else { ancestor = ancestor.parent; } } return NO; } - (NSArray *)compileExamplesWithStack:(NSArray *)stack { BOOL groupIsFocusedOrHasFocusedAncestor = [self isFocusedOrHasFocusedAncestor]; NSArray *compiled = @[]; for(id child in self.children) { if ([child isKindOfClass:[SPTExampleGroup class]]) { SPTExampleGroup *group = child; NSArray *newStack = [stack arrayByAddingObject:group]; compiled = [compiled arrayByAddingObjectsFromArray:[group compileExamplesWithStack:newStack]]; } else if ([child isKindOfClass:[SPTExample class]]) { SPTExample *example = child; NSArray *newStack = [stack arrayByAddingObject:example]; NSString *compiledName = [spt_map(newStack, ^id(id obj, NSUInteger idx) { return [obj name]; }) componentsJoinedByString:@" "]; NSString *testCaseName = [spt_map(newStack, ^id(id obj, NSUInteger idx) { return spt_underscorize([obj name]); }) componentsJoinedByString:@"__"]; // If example is pending, run only before- and after-all hooks. // Otherwise, run the example and all before and after hooks. SPTSpecBlock compiledBlock = example.pending ? ^(SPTSpec *spec){ @synchronized(self.root) { [self resetRanExampleCountIfNeeded]; [self runBeforeAllHooks:compiledName]; [self incrementRanExampleCount]; [self runAfterAllHooks:compiledName]; } } : ^(SPTSpec *spec) { @synchronized(self.root) { [self resetRanExampleCountIfNeeded]; [self runBeforeHooks:compiledName]; } @try { runExampleBlock(example.block, compiledName); } @catch(NSException *e) { [spec spt_handleException:e]; } @finally { @synchronized(self.root) { [self incrementRanExampleCount]; [self runAfterHooks:compiledName]; } } }; SPTCompiledExample *compiledExample = [[SPTCompiledExample alloc] initWithName:compiledName testCaseName:testCaseName block:compiledBlock pending:example.pending focused:(groupIsFocusedOrHasFocusedAncestor || example.focused)]; compiled = [compiled arrayByAddingObject:compiledExample]; } } return compiled; } @end ================================================ FILE: Example/Pods/Specta/Specta/Specta/SPTExcludeGlobalBeforeAfterEach.h ================================================ /* * Copyright (c) 2015 Specta Team. All rights reserved. */ #import // This protocol was used for blacklisting classes for global beforeEach and afterEach blocks. // Now, instead, classes are whitelisted by implementing the SPTGlobalBeforeAfterEach protocol. __deprecated_msg("Please whitelist classes instead with the SPTGlobalBeforeAfterEach protocol") @protocol SPTExcludeGlobalBeforeAfterEach @end ================================================ FILE: Example/Pods/Specta/Specta/Specta/SPTGlobalBeforeAfterEach.h ================================================ /* * Copyright (c) 2015 Specta Team. All rights reserved. */ #import // This protocol is used for whitelisting classes for global beforeEach and afterEach blocks. // If you want a class to participate in those just add this protocol to a category and it will be // included. @protocol SPTGlobalBeforeAfterEach @optional + (void)beforeEach; + (void)afterEach; @end ================================================ FILE: Example/Pods/Specta/Specta/Specta/SPTSharedExampleGroups.h ================================================ #import #import #import @class _XCTestCaseImplementation; @class SPTExampleGroup; @interface SPTSharedExampleGroups : XCTestCase + (void)addSharedExampleGroupWithName:(NSString *)name block:(SPTDictionaryBlock)block exampleGroup:(SPTExampleGroup *)exampleGroup; + (SPTDictionaryBlock)sharedExampleGroupWithName:(NSString *)name exampleGroup:(SPTExampleGroup *)exampleGroup; - (void)sharedExampleGroups; @end ================================================ FILE: Example/Pods/Specta/Specta/Specta/SPTSharedExampleGroups.m ================================================ #import "SPTSharedExampleGroups.h" #import "SPTExampleGroup.h" #import "SPTSpec.h" #import "SpectaUtility.h" #import NSMutableDictionary *globalSharedExampleGroups = nil; BOOL initialized = NO; @implementation SPTSharedExampleGroups + (void)initialize { Class SPTSharedExampleGroupsClass = [SPTSharedExampleGroups class]; if ([self class] == SPTSharedExampleGroupsClass) { if (!initialized) { initialized = YES; globalSharedExampleGroups = [[NSMutableDictionary alloc] init]; Class *classes = NULL; int numClasses = objc_getClassList(NULL, 0); if (numClasses > 0) { classes = (Class *)malloc(sizeof(Class) * numClasses); numClasses = objc_getClassList(classes, numClasses); Class klass, superClass; for(uint i = 0; i < numClasses; i++) { klass = classes[i]; superClass = class_getSuperclass(klass); if (superClass == SPTSharedExampleGroupsClass) { [[[klass alloc] init] sharedExampleGroups]; } } free(classes); } } } } + (void)addSharedExampleGroupWithName:(NSString *)name block:(SPTDictionaryBlock)block exampleGroup:(SPTExampleGroup *)exampleGroup { (exampleGroup == nil ? globalSharedExampleGroups : exampleGroup.sharedExamples)[name] = [block copy]; } + (SPTDictionaryBlock)sharedExampleGroupWithName:(NSString *)name exampleGroup:(SPTExampleGroup *)exampleGroup { SPTDictionaryBlock sharedExampleGroup = nil; while (exampleGroup != nil) { if ((sharedExampleGroup = exampleGroup.sharedExamples[name])) { return sharedExampleGroup; } exampleGroup = exampleGroup.parent; } return globalSharedExampleGroups[name]; } - (void)sharedExampleGroups {} - (void)spt_handleException:(NSException *)exception { [SPTCurrentSpec spt_handleException:exception]; } - (void)recordFailureWithDescription:(NSString *)description inFile:(NSString *)filename atLine:(NSUInteger)lineNumber expected:(BOOL)expected { [SPTCurrentSpec recordFailureWithDescription:description inFile:filename atLine:lineNumber expected:expected]; } - (void)_recordUnexpectedFailureWithDescription:(NSString *)description exception:(NSException *)exception { [SPTCurrentSpec _recordUnexpectedFailureWithDescription:description exception:exception]; } - (_XCTestCaseImplementation *)internalImplementation { return [SPTCurrentSpec internalImplementation]; } @end ================================================ FILE: Example/Pods/Specta/Specta/Specta/SPTSpec.h ================================================ #import #import @class SPTTestSuite , SPTCompiledExample ; @interface SPTSpec : XCTestCase @property (strong) XCTestCaseRun *spt_run; @property (nonatomic) BOOL spt_pending; @property (nonatomic) BOOL spt_skipped; + (BOOL)spt_isDisabled; + (void)spt_setDisabled:(BOOL)disabled; + (BOOL)spt_focusedExamplesExist; + (SEL)spt_convertToTestMethod:(SPTCompiledExample *)example; + (SPTTestSuite *)spt_testSuite; + (void)spt_setCurrentTestSuite; + (void)spt_unsetCurrentTestSuite; + (void)spt_setCurrentTestSuiteFileName:(NSString *)fileName lineNumber:(NSUInteger)lineNumber; - (void)spec; - (BOOL)spt_shouldRunExample:(SPTCompiledExample *)example; - (void)spt_runExample:(SPTCompiledExample *)example; @end ================================================ FILE: Example/Pods/Specta/Specta/Specta/SPTSpec.m ================================================ #import "SPTSpec.h" #import "SPTTestSuite.h" #import "SPTCompiledExample.h" #import "SPTSharedExampleGroups.h" #import "SpectaUtility.h" #import #import "XCTest+Private.h" @implementation SPTSpec + (void)initialize { [SPTSharedExampleGroups initialize]; SPTTestSuite *testSuite = [[SPTTestSuite alloc] init]; SPTSpec *spec = [[[self class] alloc] init]; NSString *specName = NSStringFromClass([self class]); objc_setAssociatedObject(self, "spt_testSuite", testSuite, OBJC_ASSOCIATION_RETAIN_NONATOMIC); [self spt_setCurrentTestSuite]; @try { [spec spec]; } @catch (NSException *exception) { fprintf(stderr, "%s: An exception has occured outside of tests, aborting.\n\n%s (%s) \n", [specName UTF8String], [[exception name] UTF8String], [[exception reason] UTF8String]); if ([exception respondsToSelector:@selector(callStackSymbols)]) { NSArray *callStackSymbols = [exception callStackSymbols]; if (callStackSymbols) { NSString *callStack = [NSString stringWithFormat:@"\n Call Stack:\n %@\n", [callStackSymbols componentsJoinedByString:@"\n "]]; fprintf(stderr, "%s", [callStack UTF8String]); } } exit(1); } @finally { [self spt_unsetCurrentTestSuite]; } [testSuite compile]; [super initialize]; } + (SPTTestSuite *)spt_testSuite { return objc_getAssociatedObject(self, "spt_testSuite"); } + (BOOL)spt_isDisabled { return [self spt_testSuite].disabled; } + (void)spt_setDisabled:(BOOL)disabled { [self spt_testSuite].disabled = disabled; } + (NSArray *)spt_allSpecClasses { static NSArray *allSpecClasses = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSMutableArray *specClasses = [[NSMutableArray alloc] init]; int numberOfClasses = objc_getClassList(NULL, 0); if (numberOfClasses > 0) { Class *classes = (Class *)malloc(sizeof(Class) * numberOfClasses); numberOfClasses = objc_getClassList(classes, numberOfClasses); for (int classIndex = 0; classIndex < numberOfClasses; classIndex++) { Class aClass = classes[classIndex]; if (spt_isSpecClass(aClass)) { [specClasses addObject:aClass]; } } free(classes); } allSpecClasses = [specClasses copy]; }); return allSpecClasses; } + (BOOL)spt_focusedExamplesExist { for (Class specClass in [self spt_allSpecClasses]) { SPTTestSuite *testSuite = [specClass spt_testSuite]; if (testSuite.disabled == NO && [testSuite hasFocusedExamples]) { return YES; } } return NO; } + (SEL)spt_convertToTestMethod:(SPTCompiledExample *)example { @synchronized(example) { if (!example.testMethodSelector) { IMP imp = imp_implementationWithBlock(^(SPTSpec *self) { [self spt_runExample:example]; }); SEL sel; unsigned int i = 0; do { i++; if (i == 1) { sel = NSSelectorFromString([NSString stringWithFormat:@"test_%@", example.testCaseName]); } else { sel = NSSelectorFromString([NSString stringWithFormat:@"test_%@_%u", example.testCaseName, i]); } } while([self instancesRespondToSelector:sel]); class_addMethod(self, sel, imp, "@@:"); example.testMethodSelector = sel; } } return example.testMethodSelector; } + (void)spt_setCurrentTestSuite { SPTTestSuite *testSuite = [self spt_testSuite]; [[NSThread currentThread] threadDictionary][spt_kCurrentTestSuiteKey] = testSuite; } + (void)spt_unsetCurrentTestSuite { [[[NSThread currentThread] threadDictionary] removeObjectForKey:spt_kCurrentTestSuiteKey]; } + (void)spt_setCurrentTestSuiteFileName:(NSString *)fileName lineNumber:(NSUInteger)lineNumber { SPTTestSuite *testSuite = [self spt_testSuite]; testSuite.fileName = fileName; testSuite.lineNumber = lineNumber; } - (void)spec {} - (BOOL)spt_shouldRunExample:(SPTCompiledExample *)example { return [[self class] spt_isDisabled] == NO && (example.focused || [[self class] spt_focusedExamplesExist] == NO); } - (void)spt_runExample:(SPTCompiledExample *)example { [[NSThread currentThread] threadDictionary][spt_kCurrentSpecKey] = self; if ([self spt_shouldRunExample:example]) { self.spt_pending = example.pending; example.block(self); } else if (!example.pending) { self.spt_skipped = YES; } [[[NSThread currentThread] threadDictionary] removeObjectForKey:spt_kCurrentSpecKey]; } #pragma mark - XCTestCase overrides + (NSArray *)testInvocations { NSArray *compiledExamples = [self spt_testSuite].compiledExamples; [NSMutableArray arrayWithCapacity:[compiledExamples count]]; NSMutableSet *addedSelectors = [NSMutableSet setWithCapacity:[compiledExamples count]]; NSMutableArray *selectors = [NSMutableArray arrayWithCapacity:[compiledExamples count]]; // dynamically generate test methods with compiled examples for (SPTCompiledExample *example in compiledExamples) { SEL sel = [self spt_convertToTestMethod:example]; NSString *selName = NSStringFromSelector(sel); [selectors addObject: selName]; [addedSelectors addObject: selName]; } // look for any other test methods that may be present in class. unsigned int n; Method *imethods = class_copyMethodList(self, &n); for (NSUInteger i = 0; i < n; i++) { struct objc_method_description *desc = method_getDescription(imethods[i]); char *types = desc->types; SEL sel = desc->name; NSString *selName = NSStringFromSelector(sel); if (strcmp(types, "@@:") == 0 && [selName hasPrefix:@"test"] && ![addedSelectors containsObject:selName]) { [selectors addObject:NSStringFromSelector(sel)]; } } free(imethods); // create invocations from test method selectors NSMutableArray *invocations = [NSMutableArray arrayWithCapacity:[selectors count]]; for (NSString *selName in selectors) { SEL sel = NSSelectorFromString(selName); NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[self instanceMethodSignatureForSelector:sel]]; [inv setSelector:sel]; [invocations addObject:inv]; } return spt_shuffle(invocations); } - (void)recordFailureWithDescription:(NSString *)description inFile:(NSString *)filename atLine:(NSUInteger)lineNumber expected:(BOOL)expected { SPTSpec *currentSpec = SPTCurrentSpec; [currentSpec.spt_run recordFailureWithDescription:description inFile:filename atLine:lineNumber expected:expected]; } - (void)performTest:(XCTestRun *)run { self.spt_run = (XCTestCaseRun *)run; [super performTest:run]; } @end ================================================ FILE: Example/Pods/Specta/Specta/Specta/SPTTestSuite.h ================================================ #import @class SPTExample , SPTExampleGroup ; @interface SPTTestSuite : NSObject @property (nonatomic, strong) SPTExampleGroup *rootGroup; @property (nonatomic, strong) NSMutableArray *groupStack; @property (nonatomic, strong) NSArray *compiledExamples; @property (nonatomic, copy) NSString *fileName; @property (nonatomic) NSUInteger lineNumber; @property (nonatomic, getter = isDisabled) BOOL disabled; @property (nonatomic) BOOL hasFocusedExamples; - (SPTExampleGroup *)currentGroup; - (void)compile; @end ================================================ FILE: Example/Pods/Specta/Specta/Specta/SPTTestSuite.m ================================================ #import "SPTTestSuite.h" #import "SPTExampleGroup.h" #import "SPTCompiledExample.h" @implementation SPTTestSuite - (id)init { self = [super init]; if (self) { self.rootGroup = [[SPTExampleGroup alloc] init]; self.rootGroup.root = self.rootGroup; self.groupStack = [NSMutableArray arrayWithObject:self.rootGroup]; } return self; } - (SPTExampleGroup *)currentGroup { return [self.groupStack lastObject]; } - (void)compile { self.compiledExamples = [self.rootGroup compileExamplesWithStack:@[]]; for (SPTCompiledExample *example in self.compiledExamples) { if (example.focused) { self.hasFocusedExamples = YES; break; } } } @end ================================================ FILE: Example/Pods/Specta/Specta/Specta/Specta.h ================================================ #import #import //! Project version number for Specta. FOUNDATION_EXPORT double SpectaVersionNumber; //! Project version string for Specta. FOUNDATION_EXPORT const unsigned char SpectaVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import #import #import #import ================================================ FILE: Example/Pods/Specta/Specta/Specta/SpectaDSL.h ================================================ #import #define SpecBegin(name) _SPTSpecBegin(name, __FILE__, __LINE__) #define SpecEnd _SPTSpecEnd #define SharedExamplesBegin(name) _SPTSharedExampleGroupsBegin(name) #define SharedExamplesEnd _SPTSharedExampleGroupsEnd #define SharedExampleGroupsBegin(name) _SPTSharedExampleGroupsBegin(name) #define SharedExampleGroupsEnd _SPTSharedExampleGroupsEnd typedef void (^DoneCallback)(void); OBJC_EXTERN void describe(NSString *name, void (^block)()); OBJC_EXTERN void fdescribe(NSString *name, void (^block)()); OBJC_EXTERN void context(NSString *name, void (^block)()); OBJC_EXTERN void fcontext(NSString *name, void (^block)()); OBJC_EXTERN void it(NSString *name, void (^block)()); OBJC_EXTERN void fit(NSString *name, void (^block)()); OBJC_EXTERN void example(NSString *name, void (^block)()); OBJC_EXTERN void fexample(NSString *name, void (^block)()); OBJC_EXTERN void specify(NSString *name, void (^block)()); OBJC_EXTERN void fspecify(NSString *name, void (^block)()); #define pending(...) spt_pending_(__VA_ARGS__, nil) #define xdescribe(...) spt_pending_(__VA_ARGS__, nil) #define xcontext(...) spt_pending_(__VA_ARGS__, nil) #define xexample(...) spt_pending_(__VA_ARGS__, nil) #define xit(...) spt_pending_(__VA_ARGS__, nil) #define xspecify(...) spt_pending_(__VA_ARGS__, nil) OBJC_EXTERN void beforeAll(void (^block)()); OBJC_EXTERN void afterAll(void (^block)()); OBJC_EXTERN void beforeEach(void (^block)()); OBJC_EXTERN void afterEach(void (^block)()); OBJC_EXTERN void before(void (^block)()); OBJC_EXTERN void after(void (^block)()); OBJC_EXTERN void sharedExamplesFor(NSString *name, void (^block)(NSDictionary *data)); OBJC_EXTERN void sharedExamples(NSString *name, void (^block)(NSDictionary *data)); #define itShouldBehaveLike(...) spt_itShouldBehaveLike_(@(__FILE__), __LINE__, __VA_ARGS__) #define itBehavesLike(...) spt_itShouldBehaveLike_(@(__FILE__), __LINE__, __VA_ARGS__) OBJC_EXTERN void waitUntil(void (^block)(DoneCallback done)); /** * Runs the @c block and waits until the @c done block is called or the * @c timeout has passed. * * @param timeout timeout for this @c block only; does not affect the global * timeout, as @c setAsyncSpecTimeout() does. * @param ^block runs test code */ OBJC_EXTERN void waitUntilTimeout(NSTimeInterval timeout, void (^block)(DoneCallback done)); OBJC_EXTERN void setAsyncSpecTimeout(NSTimeInterval timeout); // ---------------------------------------------------------------------------- #define _SPTSpecBegin(name, file, line) \ @interface name##Spec : SPTSpec \ @end \ @implementation name##Spec \ - (void)spec { \ [[self class] spt_setCurrentTestSuiteFileName:(@(file)) lineNumber:(line)]; #define _SPTSpecEnd \ } \ @end #define _SPTSharedExampleGroupsBegin(name) \ @interface name##SharedExampleGroups : SPTSharedExampleGroups \ @end \ @implementation name##SharedExampleGroups \ - (void)sharedExampleGroups { #define _SPTSharedExampleGroupsEnd \ } \ @end OBJC_EXTERN void spt_it_(NSString *name, NSString *fileName, NSUInteger lineNumber, void (^block)()); OBJC_EXTERN void spt_fit_(NSString *name, NSString *fileName, NSUInteger lineNumber, void (^block)()); OBJC_EXTERN void spt_pending_(NSString *name, ...); OBJC_EXTERN void spt_itShouldBehaveLike_(NSString *fileName, NSUInteger lineNumber, NSString *name, id dictionaryOrBlock); OBJC_EXTERN void spt_itShouldBehaveLike_block(NSString *fileName, NSUInteger lineNumber, NSString *name, NSDictionary *(^block)()); ================================================ FILE: Example/Pods/Specta/Specta/Specta/SpectaDSL.m ================================================ #import "SpectaDSL.h" #import "SpectaTypes.h" #import "SpectaUtility.h" #import "SPTTestSuite.h" #import "SPTExampleGroup.h" #import "SPTSharedExampleGroups.h" #import "SPTSpec.h" #import "SPTCallSite.h" #import static NSTimeInterval asyncSpecTimeout = 10.0; static void spt_defineItBlock(NSString *name, NSString *fileName, NSUInteger lineNumber, BOOL focused, void (^block)()) { SPTReturnUnlessBlockOrNil(block); SPTCallSite *site = nil; if (lineNumber && fileName) { site = [SPTCallSite callSiteWithFile:fileName line:lineNumber]; } [SPTCurrentGroup addExampleWithName:name callSite:site focused:focused block:block]; } static void spt_defineDescribeBlock(NSString *name, BOOL focused, void (^block)()) { if (block) { [SPTGroupStack addObject:[SPTCurrentGroup addExampleGroupWithName:name focused:focused]]; block(); [SPTGroupStack removeLastObject]; } else { spt_defineItBlock(name, nil, 0, focused, nil); } } void spt_it_(NSString *name, NSString *fileName, NSUInteger lineNumber, void (^block)()) { spt_defineItBlock(name, fileName, lineNumber, NO, block); } void spt_fit_(NSString *name, NSString *fileName, NSUInteger lineNumber, void (^block)()) { spt_defineItBlock(name, fileName, lineNumber, YES, block); } void spt_pending_(NSString *name, ...) { spt_defineItBlock(name, nil, 0, NO, nil); } void spt_itShouldBehaveLike_(NSString *fileName, NSUInteger lineNumber, NSString *name, id dictionaryOrBlock) { SPTDictionaryBlock block = [SPTSharedExampleGroups sharedExampleGroupWithName:name exampleGroup:SPTCurrentGroup]; if (block) { if (SPTIsBlock(dictionaryOrBlock)) { id (^dataBlock)(void) = [dictionaryOrBlock copy]; describe(name, ^{ __block NSMutableDictionary *dataDict = [[NSMutableDictionary alloc] init]; beforeEach(^{ NSDictionary *blockData = dataBlock(); [dataDict removeAllObjects]; [dataDict addEntriesFromDictionary:blockData]; }); block(dataDict); afterAll(^{ dataDict = nil; }); }); } else { NSDictionary *data = dictionaryOrBlock; describe(name, ^{ block(data); }); } } else { SPTSpec *currentSpec = SPTCurrentSpec; if (currentSpec) { [currentSpec recordFailureWithDescription:@"itShouldBehaveLike should not be invoked inside an example block!" inFile:fileName atLine:lineNumber expected:NO]; } else { it(name, ^{ [SPTCurrentSpec recordFailureWithDescription:[NSString stringWithFormat:@"Shared example group \"%@\" does not exist.", name] inFile:fileName atLine:lineNumber expected:NO]; }); } } } void spt_itShouldBehaveLike_block(NSString *fileName, NSUInteger lineNumber, NSString *name, NSDictionary *(^block)()) { spt_itShouldBehaveLike_(fileName, lineNumber, name, (id)block); } void describe(NSString *name, void (^block)()) { spt_defineDescribeBlock(name, NO, block); } void fdescribe(NSString *name, void (^block)()) { spt_defineDescribeBlock(name, YES, block); } void context(NSString *name, void (^block)()) { describe(name, block); } void fcontext(NSString *name, void (^block)()) { fdescribe(name, block); } void it(NSString *name, void (^block)()) { spt_defineItBlock(name, nil, 0, NO, block); } void fit(NSString *name, void (^block)()) { spt_defineItBlock(name, nil, 0, YES, block); } void example(NSString *name, void (^block)()) { it(name, block); } void fexample(NSString *name, void (^block)()) { fit(name, block); } void specify(NSString *name, void (^block)()) { it(name, block); } void fspecify(NSString *name, void (^block)()) { fit(name, block); } void beforeAll(void (^block)()) { SPTReturnUnlessBlockOrNil(block); [SPTCurrentGroup addBeforeAllBlock:block]; } void afterAll(void (^block)()) { SPTReturnUnlessBlockOrNil(block); [SPTCurrentGroup addAfterAllBlock:block]; } void beforeEach(void (^block)()) { SPTReturnUnlessBlockOrNil(block); [SPTCurrentGroup addBeforeEachBlock:block]; } void afterEach(void (^block)()) { SPTReturnUnlessBlockOrNil(block); [SPTCurrentGroup addAfterEachBlock:block]; } void before(void (^block)()) { beforeEach(block); } void after(void (^block)()) { afterEach(block); } void sharedExamplesFor(NSString *name, void (^block)(NSDictionary *data)) { [SPTSharedExampleGroups addSharedExampleGroupWithName:name block:block exampleGroup:SPTCurrentGroup]; } void sharedExamples(NSString *name, void (^block)(NSDictionary *data)) { sharedExamplesFor(name, block); } void waitUntil(void (^block)(DoneCallback done)) { waitUntilTimeout(asyncSpecTimeout, block); } void waitUntilTimeout(NSTimeInterval timeout, void (^block)(DoneCallback done)) { __block uint32_t complete = 0; dispatch_async(dispatch_get_main_queue(), ^{ block(^{ OSAtomicOr32Barrier(1, &complete); }); }); NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeout]; while (!complete && [timeoutDate timeIntervalSinceNow] > 0) { [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]]; } if (!complete) { NSString *message = [NSString stringWithFormat:@"failed to invoke done() callback before timeout (%f seconds)", timeout]; SPTSpec *currentSpec = SPTCurrentSpec; SPTTestSuite *testSuite = [[currentSpec class] spt_testSuite]; [currentSpec recordFailureWithDescription:message inFile:testSuite.fileName atLine:testSuite.lineNumber expected:YES]; } } void setAsyncSpecTimeout(NSTimeInterval timeout) { asyncSpecTimeout = timeout; } ================================================ FILE: Example/Pods/Specta/Specta/Specta/SpectaTypes.h ================================================ @class SPTSpec; typedef void (^SPTVoidBlock)(); typedef void (^SPTSpecBlock)(SPTSpec *spec); typedef void (^SPTDictionaryBlock)(NSDictionary *dictionary); ================================================ FILE: Example/Pods/Specta/Specta/Specta/SpectaUtility.h ================================================ #import extern NSString * const spt_kCurrentTestSuiteKey; extern NSString * const spt_kCurrentSpecKey; #define SPTCurrentTestSuite [[NSThread mainThread] threadDictionary][spt_kCurrentTestSuiteKey] #define SPTCurrentSpec [[NSThread mainThread] threadDictionary][spt_kCurrentSpecKey] #define SPTCurrentGroup [SPTCurrentTestSuite currentGroup] #define SPTGroupStack [SPTCurrentTestSuite groupStack] #define SPTReturnUnlessBlockOrNil(block) if ((block) && !SPTIsBlock((block))) return; #define SPTIsBlock(obj) [(obj) isKindOfClass:NSClassFromString(@"NSBlock")] BOOL spt_isSpecClass(Class aClass); NSString *spt_underscorize(NSString *string); NSArray *spt_map(NSArray *array, id (^block)(id obj, NSUInteger idx)); NSArray *spt_shuffle(NSArray *array); unsigned int spt_seed(); ================================================ FILE: Example/Pods/Specta/Specta/Specta/SpectaUtility.m ================================================ #import "SpectaUtility.h" #import "SPTSpec.h" #import NSString * const spt_kCurrentTestSuiteKey = @"SPTCurrentTestSuite"; NSString * const spt_kCurrentSpecKey = @"SPTCurrentSpec"; static unsigned int seed = 0; BOOL spt_isSpecClass(Class aClass) { Class superclass = class_getSuperclass(aClass); while (superclass != Nil) { if (superclass == [SPTSpec class]) { return YES; } else { superclass = class_getSuperclass(superclass); } } return NO; } NSString *spt_underscorize(NSString *string) { static NSMutableCharacterSet *invalidCharSet; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ invalidCharSet = [[NSMutableCharacterSet alloc] init]; [invalidCharSet formUnionWithCharacterSet:[NSCharacterSet controlCharacterSet]]; [invalidCharSet formUnionWithCharacterSet:[NSCharacterSet illegalCharacterSet]]; [invalidCharSet formUnionWithCharacterSet:[NSCharacterSet newlineCharacterSet]]; [invalidCharSet formUnionWithCharacterSet:[NSCharacterSet nonBaseCharacterSet]]; [invalidCharSet formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]]; [invalidCharSet formUnionWithCharacterSet:[NSCharacterSet symbolCharacterSet]]; }); NSString *stripped = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; stripped = [[stripped componentsSeparatedByCharactersInSet:invalidCharSet] componentsJoinedByString:@""]; NSArray *components = [stripped componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; stripped = [[components objectsAtIndexes:[components indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) { return ![obj isEqualToString:@""]; }]] componentsJoinedByString:@"_"]; return stripped; } NSArray *spt_map(NSArray *array, id (^block)(id obj, NSUInteger idx)) { NSMutableArray *mapped = [NSMutableArray arrayWithCapacity:[array count]]; [array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { [mapped addObject:block(obj, idx)]; }]; return mapped; } NSArray *spt_shuffle(NSArray *array) { if (![[[[NSProcessInfo processInfo] environment] objectForKey:@"SPECTA_SHUFFLE"] isEqualToString:@"1"]) { return array; } spt_seed(); NSMutableArray *shuffled = [array mutableCopy]; NSUInteger count = [shuffled count]; for (NSUInteger i = 0; i < count; i++) { NSUInteger r = random() % count; [shuffled exchangeObjectAtIndex:i withObjectAtIndex:r]; } return shuffled; } unsigned int spt_seed() { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSString *envSeed = [[[NSProcessInfo processInfo] environment] objectForKey:@"SPECTA_SEED"]; if (envSeed) { sscanf([envSeed UTF8String], "%u", &seed); } else { seed = arc4random(); } srandom(seed); printf("Test Seed: %u\n", seed); }); return seed; } ================================================ FILE: Example/Pods/Specta/Specta/Specta/XCTest+Private.h ================================================ #import #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 90000 || __MAC_OS_X_VERSION_MAX_ALLOWED >= 101100 @interface XCTestObservationCenter (SPTTestSuspention) - (void)_suspendObservationForBlock:(void (^)(void))block; @end #else @interface XCTestObservationCenter : NSObject + (id)sharedObservationCenter; - (void)_suspendObservationForBlock:(void (^)(void))block; @end #endif @protocol XCTestObservation @end @interface _XCTestDriverTestObserver : NSObject - (void)stopObserving; - (void)startObserving; @end @interface _XCTestCaseImplementation : NSObject @end @interface XCTestCase () - (_XCTestCaseImplementation *)internalImplementation; - (void)_recordUnexpectedFailureWithDescription:(NSString *)description exception:(NSException *)exception; @end ================================================ FILE: Example/Pods/Specta/Specta/Specta/XCTestCase+Specta.h ================================================ #import @interface XCTestCase (Specta) - (void)spt_handleException:(NSException *)exception; @end ================================================ FILE: Example/Pods/Specta/Specta/Specta/XCTestCase+Specta.m ================================================ #import #import "XCTestCase+Specta.h" #import "SPTSpec.h" #import "SPTExample.h" #import "SPTSharedExampleGroups.h" #import "SpectaUtility.h" #import "XCTest+Private.h" @interface XCTestCase (xct_allSubclasses) - (NSArray *)allSubclasses; - (void)_dequeueFailures; @end @implementation XCTestCase (Specta) + (void)load { Method allSubclasses = class_getClassMethod(self, @selector(allSubclasses)); Method allSubclasses_swizzle = class_getClassMethod(self , @selector(spt_allSubclasses_swizzle)); method_exchangeImplementations(allSubclasses, allSubclasses_swizzle); Method dequeueFailures = class_getInstanceMethod(self, @selector(_dequeueFailures)); Method dequeueFailures_swizzle = class_getInstanceMethod(self, @selector(spt_dequeueFailures)); method_exchangeImplementations(dequeueFailures, dequeueFailures_swizzle); } + (NSArray *)spt_allSubclasses_swizzle { NSArray *subclasses = [self spt_allSubclasses_swizzle]; // call original NSMutableArray *filtered = [NSMutableArray arrayWithCapacity:[subclasses count]]; // exclude SPTSpec base class and all subclasses of SPTSharedExampleGroups for (id subclass in subclasses) { if (subclass != [SPTSpec class] && ![subclass isKindOfClass:[SPTSharedExampleGroups class]]) { [filtered addObject:subclass]; } } return spt_shuffle(filtered); } - (void)spt_dequeueFailures { void(^dequeueFailures)() = ^() { [self spt_dequeueFailures]; }; if ([NSThread isMainThread]) { dequeueFailures(); } else { dispatch_sync(dispatch_get_main_queue(), dequeueFailures); } } - (void)spt_handleException:(NSException *)exception { NSString *description = [exception reason]; if ([exception userInfo]) { id line = [exception userInfo][@"line"]; id file = [exception userInfo][@"file"]; if ([line isKindOfClass:[NSNumber class]] && [file isKindOfClass:[NSString class]]) { [self recordFailureWithDescription:description inFile:file atLine:[line unsignedIntegerValue] expected:YES]; return; } } [self _recordUnexpectedFailureWithDescription:description exception:exception]; } @end ================================================ FILE: Example/Pods/Target Support Files/DACircularProgress/DACircularProgress-dummy.m ================================================ #import @interface PodsDummy_DACircularProgress : NSObject @end @implementation PodsDummy_DACircularProgress @end ================================================ FILE: Example/Pods/Target Support Files/DACircularProgress/DACircularProgress-prefix.pch ================================================ #ifdef __OBJC__ #import #endif ================================================ FILE: Example/Pods/Target Support Files/DACircularProgress/DACircularProgress.xcconfig ================================================ GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/DACircularProgress" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/DACircularProgress" "${PODS_ROOT}/Headers/Public/Expecta" "${PODS_ROOT}/Headers/Public/Expecta+Snapshots" "${PODS_ROOT}/Headers/Public/FBSnapshotTestCase" "${PODS_ROOT}/Headers/Public/MBProgressHUD" "${PODS_ROOT}/Headers/Public/MWPhotoBrowser" "${PODS_ROOT}/Headers/Public/SDWebImage" "${PODS_ROOT}/Headers/Public/Specta" OTHER_LDFLAGS = -framework "QuartzCore" PODS_ROOT = ${SRCROOT} SKIP_INSTALL = YES ================================================ FILE: Example/Pods/Target Support Files/Expecta/Expecta-dummy.m ================================================ #import @interface PodsDummy_Expecta : NSObject @end @implementation PodsDummy_Expecta @end ================================================ FILE: Example/Pods/Target Support Files/Expecta/Expecta-prefix.pch ================================================ #ifdef __OBJC__ #import #endif ================================================ FILE: Example/Pods/Target Support Files/Expecta/Expecta.xcconfig ================================================ FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" $(inherited) $(DEVELOPER_FRAMEWORKS_DIR) "$(PLATFORM_DIR)/Developer/Library/Frameworks" "$(DEVELOPER_DIR)/Platforms/iPhoneSimulator.platform/Developer/Library/Frameworks" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/Expecta" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/DACircularProgress" "${PODS_ROOT}/Headers/Public/Expecta" "${PODS_ROOT}/Headers/Public/Expecta+Snapshots" "${PODS_ROOT}/Headers/Public/FBSnapshotTestCase" "${PODS_ROOT}/Headers/Public/MBProgressHUD" "${PODS_ROOT}/Headers/Public/MWPhotoBrowser" "${PODS_ROOT}/Headers/Public/SDWebImage" "${PODS_ROOT}/Headers/Public/Specta" OTHER_LDFLAGS = -framework "Foundation" -framework "XCTest" PODS_ROOT = ${SRCROOT} SKIP_INSTALL = YES ================================================ FILE: Example/Pods/Target Support Files/Expecta+Snapshots/Expecta+Snapshots-dummy.m ================================================ #import @interface PodsDummy_Expecta_Snapshots : NSObject @end @implementation PodsDummy_Expecta_Snapshots @end ================================================ FILE: Example/Pods/Target Support Files/Expecta+Snapshots/Expecta+Snapshots-prefix.pch ================================================ #ifdef __OBJC__ #import #endif ================================================ FILE: Example/Pods/Target Support Files/Expecta+Snapshots/Expecta+Snapshots.xcconfig ================================================ FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/Expecta+Snapshots" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/DACircularProgress" "${PODS_ROOT}/Headers/Public/Expecta" "${PODS_ROOT}/Headers/Public/Expecta+Snapshots" "${PODS_ROOT}/Headers/Public/FBSnapshotTestCase" "${PODS_ROOT}/Headers/Public/MBProgressHUD" "${PODS_ROOT}/Headers/Public/MWPhotoBrowser" "${PODS_ROOT}/Headers/Public/SDWebImage" "${PODS_ROOT}/Headers/Public/Specta" OTHER_LDFLAGS = -framework "Foundation" -framework "XCTest" PODS_ROOT = ${SRCROOT} SKIP_INSTALL = YES ================================================ FILE: Example/Pods/Target Support Files/FBSnapshotTestCase/FBSnapshotTestCase-dummy.m ================================================ #import @interface PodsDummy_FBSnapshotTestCase : NSObject @end @implementation PodsDummy_FBSnapshotTestCase @end ================================================ FILE: Example/Pods/Target Support Files/FBSnapshotTestCase/FBSnapshotTestCase-prefix.pch ================================================ #ifdef __OBJC__ #import #endif ================================================ FILE: Example/Pods/Target Support Files/FBSnapshotTestCase/FBSnapshotTestCase.xcconfig ================================================ FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/FBSnapshotTestCase" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/DACircularProgress" "${PODS_ROOT}/Headers/Public/Expecta" "${PODS_ROOT}/Headers/Public/Expecta+Snapshots" "${PODS_ROOT}/Headers/Public/FBSnapshotTestCase" "${PODS_ROOT}/Headers/Public/MBProgressHUD" "${PODS_ROOT}/Headers/Public/MWPhotoBrowser" "${PODS_ROOT}/Headers/Public/SDWebImage" "${PODS_ROOT}/Headers/Public/Specta" OTHER_LDFLAGS = -framework "XCTest" PODS_ROOT = ${SRCROOT} SKIP_INSTALL = YES ================================================ FILE: Example/Pods/Target Support Files/MBProgressHUD/MBProgressHUD-dummy.m ================================================ #import @interface PodsDummy_MBProgressHUD : NSObject @end @implementation PodsDummy_MBProgressHUD @end ================================================ FILE: Example/Pods/Target Support Files/MBProgressHUD/MBProgressHUD-prefix.pch ================================================ #ifdef __OBJC__ #import #endif ================================================ FILE: Example/Pods/Target Support Files/MBProgressHUD/MBProgressHUD.xcconfig ================================================ GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/MBProgressHUD" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/DACircularProgress" "${PODS_ROOT}/Headers/Public/Expecta" "${PODS_ROOT}/Headers/Public/Expecta+Snapshots" "${PODS_ROOT}/Headers/Public/FBSnapshotTestCase" "${PODS_ROOT}/Headers/Public/MBProgressHUD" "${PODS_ROOT}/Headers/Public/MWPhotoBrowser" "${PODS_ROOT}/Headers/Public/SDWebImage" "${PODS_ROOT}/Headers/Public/Specta" OTHER_LDFLAGS = -framework "CoreGraphics" PODS_ROOT = ${SRCROOT} SKIP_INSTALL = YES ================================================ FILE: Example/Pods/Target Support Files/MWPhotoBrowser/MWPhotoBrowser-dummy.m ================================================ #import @interface PodsDummy_MWPhotoBrowser : NSObject @end @implementation PodsDummy_MWPhotoBrowser @end ================================================ FILE: Example/Pods/Target Support Files/MWPhotoBrowser/MWPhotoBrowser-prefix.pch ================================================ #ifdef __OBJC__ #import #endif ================================================ FILE: Example/Pods/Target Support Files/MWPhotoBrowser/MWPhotoBrowser.xcconfig ================================================ GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/MWPhotoBrowser" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/DACircularProgress" "${PODS_ROOT}/Headers/Public/Expecta" "${PODS_ROOT}/Headers/Public/Expecta+Snapshots" "${PODS_ROOT}/Headers/Public/FBSnapshotTestCase" "${PODS_ROOT}/Headers/Public/MBProgressHUD" "${PODS_ROOT}/Headers/Public/MWPhotoBrowser" "${PODS_ROOT}/Headers/Public/SDWebImage" "${PODS_ROOT}/Headers/Public/Specta" OTHER_LDFLAGS = -framework "AssetsLibrary" -framework "ImageIO" -framework "MediaPlayer" -framework "QuartzCore" -weak_framework "Photos" PODS_ROOT = ${SRCROOT} SKIP_INSTALL = YES ================================================ FILE: Example/Pods/Target Support Files/Pods-MWPhotoBrowser_Example/Pods-MWPhotoBrowser_Example-acknowledgements.markdown ================================================ # Acknowledgements This application makes use of the following third party libraries: ## DACircularProgress # License ## MIT License Copyright (c) 2013 Daniel Amitay (http://danielamitay.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## MBProgressHUD Copyright (c) 2009-2015 Matej Bukovinski Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## MWPhotoBrowser Copyright (c) 2010 Michael Waterfall Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## SDWebImage Copyright (c) 2009 Olivier Poitrey Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Generated by CocoaPods - http://cocoapods.org ================================================ FILE: Example/Pods/Target Support Files/Pods-MWPhotoBrowser_Example/Pods-MWPhotoBrowser_Example-acknowledgements.plist ================================================ PreferenceSpecifiers FooterText This application makes use of the following third party libraries: Title Acknowledgements Type PSGroupSpecifier FooterText # License ## MIT License Copyright (c) 2013 Daniel Amitay (http://danielamitay.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Title DACircularProgress Type PSGroupSpecifier FooterText Copyright (c) 2009-2015 Matej Bukovinski Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Title MBProgressHUD Type PSGroupSpecifier FooterText Copyright (c) 2010 Michael Waterfall <michaelwaterfall@gmail.com> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Title MWPhotoBrowser Type PSGroupSpecifier FooterText Copyright (c) 2009 Olivier Poitrey <rs@dailymotion.com> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Title SDWebImage Type PSGroupSpecifier FooterText Generated by CocoaPods - http://cocoapods.org Title Type PSGroupSpecifier StringsTable Acknowledgements Title Acknowledgements ================================================ FILE: Example/Pods/Target Support Files/Pods-MWPhotoBrowser_Example/Pods-MWPhotoBrowser_Example-dummy.m ================================================ #import @interface PodsDummy_Pods_MWPhotoBrowser_Example : NSObject @end @implementation PodsDummy_Pods_MWPhotoBrowser_Example @end ================================================ FILE: Example/Pods/Target Support Files/Pods-MWPhotoBrowser_Example/Pods-MWPhotoBrowser_Example-frameworks.sh ================================================ #!/bin/sh set -e echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" install_framework() { if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then local source="${BUILT_PRODUCTS_DIR}/$1" elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" elif [ -r "$1" ]; then local source="$1" fi local destination="${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" if [ -L "${source}" ]; then echo "Symlinked..." source="$(readlink "${source}")" fi # use filter instead of exclude so missing patterns dont' throw errors echo "rsync -av --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" rsync -av --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" local basename basename="$(basename -s .framework "$1")" binary="${destination}/${basename}.framework/${basename}" if ! [ -r "$binary" ]; then binary="${destination}/${basename}" fi # Strip invalid architectures so "fat" simulator / device frameworks work on device if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then strip_invalid_archs "$binary" fi # Resign the code if required by the build settings to avoid unstable apps code_sign_if_enabled "${destination}/$(basename "$1")" # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then local swift_runtime_libs swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]}) for lib in $swift_runtime_libs; do echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" code_sign_if_enabled "${destination}/${lib}" done fi } # Signs a framework with the provided identity code_sign_if_enabled() { if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then # Use the current code_sign_identitiy echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" echo "/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} --preserve-metadata=identifier,entitlements \"$1\"" /usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} --preserve-metadata=identifier,entitlements "$1" fi } # Strip invalid architectures strip_invalid_archs() { binary="$1" # Get architectures for current file archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)" stripped="" for arch in $archs; do if ! [[ "${VALID_ARCHS}" == *"$arch"* ]]; then # Strip non-valid architectures in-place lipo -remove "$arch" -output "$binary" "$binary" || exit 1 stripped="$stripped $arch" fi done if [[ "$stripped" ]]; then echo "Stripped $binary of architectures:$stripped" fi } ================================================ FILE: Example/Pods/Target Support Files/Pods-MWPhotoBrowser_Example/Pods-MWPhotoBrowser_Example-resources.sh ================================================ #!/bin/sh set -e mkdir -p "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt > "$RESOURCES_TO_COPY" XCASSET_FILES=() realpath() { DIRECTORY="$(cd "${1%/*}" && pwd)" FILENAME="${1##*/}" echo "$DIRECTORY/$FILENAME" } install_resource() { case $1 in *.storyboard) echo "ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .storyboard`.storyboardc ${PODS_ROOT}/$1 --sdk ${SDKROOT}" ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .storyboard`.storyboardc" "${PODS_ROOT}/$1" --sdk "${SDKROOT}" ;; *.xib) echo "ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .xib`.nib ${PODS_ROOT}/$1 --sdk ${SDKROOT}" ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .xib`.nib" "${PODS_ROOT}/$1" --sdk "${SDKROOT}" ;; *.framework) echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" echo "rsync -av ${PODS_ROOT}/$1 ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" rsync -av "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" ;; *.xcdatamodel) echo "xcrun momc \"${PODS_ROOT}/$1\" \"${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1"`.mom\"" xcrun momc "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcdatamodel`.mom" ;; *.xcdatamodeld) echo "xcrun momc \"${PODS_ROOT}/$1\" \"${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcdatamodeld`.momd\"" xcrun momc "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcdatamodeld`.momd" ;; *.xcmappingmodel) echo "xcrun mapc \"${PODS_ROOT}/$1\" \"${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcmappingmodel`.cdm\"" xcrun mapc "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcmappingmodel`.cdm" ;; *.xcassets) ABSOLUTE_XCASSET_FILE=$(realpath "${PODS_ROOT}/$1") XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") ;; /*) echo "$1" echo "$1" >> "$RESOURCES_TO_COPY" ;; *) echo "${PODS_ROOT}/$1" echo "${PODS_ROOT}/$1" >> "$RESOURCES_TO_COPY" ;; esac } if [[ "$CONFIGURATION" == "Debug" ]]; then install_resource "${BUILT_PRODUCTS_DIR}/MWPhotoBrowser.bundle" fi if [[ "$CONFIGURATION" == "Release" ]]; then install_resource "${BUILT_PRODUCTS_DIR}/MWPhotoBrowser.bundle" fi mkdir -p "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" fi rm -f "$RESOURCES_TO_COPY" if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ] then case "${TARGETED_DEVICE_FAMILY}" in 1,2) TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" ;; 1) TARGET_DEVICE_ARGS="--target-device iphone" ;; 2) TARGET_DEVICE_ARGS="--target-device ipad" ;; *) TARGET_DEVICE_ARGS="--target-device mac" ;; esac # Find all other xcassets (this unfortunately includes those of path pods and other targets). OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) while read line; do if [[ $line != "`realpath $PODS_ROOT`*" ]]; then XCASSET_FILES+=("$line") fi done <<<"$OTHER_XCASSETS" printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${IPHONEOS_DEPLOYMENT_TARGET}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" fi ================================================ FILE: Example/Pods/Target Support Files/Pods-MWPhotoBrowser_Example/Pods-MWPhotoBrowser_Example.debug.xcconfig ================================================ GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/DACircularProgress" "${PODS_ROOT}/Headers/Public/Expecta" "${PODS_ROOT}/Headers/Public/Expecta+Snapshots" "${PODS_ROOT}/Headers/Public/FBSnapshotTestCase" "${PODS_ROOT}/Headers/Public/MBProgressHUD" "${PODS_ROOT}/Headers/Public/MWPhotoBrowser" "${PODS_ROOT}/Headers/Public/SDWebImage" "${PODS_ROOT}/Headers/Public/Specta" OTHER_CFLAGS = $(inherited) -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/DACircularProgress" -isystem "${PODS_ROOT}/Headers/Public/Expecta" -isystem "${PODS_ROOT}/Headers/Public/Expecta+Snapshots" -isystem "${PODS_ROOT}/Headers/Public/FBSnapshotTestCase" -isystem "${PODS_ROOT}/Headers/Public/MBProgressHUD" -isystem "${PODS_ROOT}/Headers/Public/MWPhotoBrowser" -isystem "${PODS_ROOT}/Headers/Public/SDWebImage" -isystem "${PODS_ROOT}/Headers/Public/Specta" OTHER_LDFLAGS = $(inherited) -ObjC -l"DACircularProgress" -l"MBProgressHUD" -l"MWPhotoBrowser" -l"SDWebImage" -framework "AssetsLibrary" -framework "CoreGraphics" -framework "ImageIO" -framework "MediaPlayer" -framework "QuartzCore" -weak_framework "Photos" PODS_ROOT = ${SRCROOT}/Pods ================================================ FILE: Example/Pods/Target Support Files/Pods-MWPhotoBrowser_Example/Pods-MWPhotoBrowser_Example.release.xcconfig ================================================ GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/DACircularProgress" "${PODS_ROOT}/Headers/Public/Expecta" "${PODS_ROOT}/Headers/Public/Expecta+Snapshots" "${PODS_ROOT}/Headers/Public/FBSnapshotTestCase" "${PODS_ROOT}/Headers/Public/MBProgressHUD" "${PODS_ROOT}/Headers/Public/MWPhotoBrowser" "${PODS_ROOT}/Headers/Public/SDWebImage" "${PODS_ROOT}/Headers/Public/Specta" OTHER_CFLAGS = $(inherited) -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/DACircularProgress" -isystem "${PODS_ROOT}/Headers/Public/Expecta" -isystem "${PODS_ROOT}/Headers/Public/Expecta+Snapshots" -isystem "${PODS_ROOT}/Headers/Public/FBSnapshotTestCase" -isystem "${PODS_ROOT}/Headers/Public/MBProgressHUD" -isystem "${PODS_ROOT}/Headers/Public/MWPhotoBrowser" -isystem "${PODS_ROOT}/Headers/Public/SDWebImage" -isystem "${PODS_ROOT}/Headers/Public/Specta" OTHER_LDFLAGS = $(inherited) -ObjC -l"DACircularProgress" -l"MBProgressHUD" -l"MWPhotoBrowser" -l"SDWebImage" -framework "AssetsLibrary" -framework "CoreGraphics" -framework "ImageIO" -framework "MediaPlayer" -framework "QuartzCore" -weak_framework "Photos" PODS_ROOT = ${SRCROOT}/Pods ================================================ FILE: Example/Pods/Target Support Files/Pods-MWPhotoBrowser_Tests/Pods-MWPhotoBrowser_Tests-acknowledgements.markdown ================================================ # Acknowledgements This application makes use of the following third party libraries: ## DACircularProgress # License ## MIT License Copyright (c) 2013 Daniel Amitay (http://danielamitay.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## MBProgressHUD Copyright (c) 2009-2015 Matej Bukovinski Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## MWPhotoBrowser Copyright (c) 2010 Michael Waterfall Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## SDWebImage Copyright (c) 2009 Olivier Poitrey Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## Expecta Copyright (c) 2011-2015 Specta Team - https://github.com/specta Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## Expecta+Snapshots MIT License Copyright (c) 2014 Daniel Doubrovkine, Artsy Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## FBSnapshotTestCase BSD License For the FBSnapshotTestCase software Copyright (c) 2013, Facebook, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name Facebook nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ## Specta Copyright (c) 2012-2014 Specta Team. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Generated by CocoaPods - http://cocoapods.org ================================================ FILE: Example/Pods/Target Support Files/Pods-MWPhotoBrowser_Tests/Pods-MWPhotoBrowser_Tests-acknowledgements.plist ================================================ PreferenceSpecifiers FooterText This application makes use of the following third party libraries: Title Acknowledgements Type PSGroupSpecifier FooterText # License ## MIT License Copyright (c) 2013 Daniel Amitay (http://danielamitay.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Title DACircularProgress Type PSGroupSpecifier FooterText Copyright (c) 2009-2015 Matej Bukovinski Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Title MBProgressHUD Type PSGroupSpecifier FooterText Copyright (c) 2010 Michael Waterfall <michaelwaterfall@gmail.com> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Title MWPhotoBrowser Type PSGroupSpecifier FooterText Copyright (c) 2009 Olivier Poitrey <rs@dailymotion.com> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Title SDWebImage Type PSGroupSpecifier FooterText Copyright (c) 2011-2015 Specta Team - https://github.com/specta Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Title Expecta Type PSGroupSpecifier FooterText MIT License Copyright (c) 2014 Daniel Doubrovkine, Artsy Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Title Expecta+Snapshots Type PSGroupSpecifier FooterText BSD License For the FBSnapshotTestCase software Copyright (c) 2013, Facebook, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name Facebook nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Title FBSnapshotTestCase Type PSGroupSpecifier FooterText Copyright (c) 2012-2014 Specta Team. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Title Specta Type PSGroupSpecifier FooterText Generated by CocoaPods - http://cocoapods.org Title Type PSGroupSpecifier StringsTable Acknowledgements Title Acknowledgements ================================================ FILE: Example/Pods/Target Support Files/Pods-MWPhotoBrowser_Tests/Pods-MWPhotoBrowser_Tests-dummy.m ================================================ #import @interface PodsDummy_Pods_MWPhotoBrowser_Tests : NSObject @end @implementation PodsDummy_Pods_MWPhotoBrowser_Tests @end ================================================ FILE: Example/Pods/Target Support Files/Pods-MWPhotoBrowser_Tests/Pods-MWPhotoBrowser_Tests-frameworks.sh ================================================ #!/bin/sh set -e echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" install_framework() { if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then local source="${BUILT_PRODUCTS_DIR}/$1" elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" elif [ -r "$1" ]; then local source="$1" fi local destination="${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" if [ -L "${source}" ]; then echo "Symlinked..." source="$(readlink "${source}")" fi # use filter instead of exclude so missing patterns dont' throw errors echo "rsync -av --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" rsync -av --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" local basename basename="$(basename -s .framework "$1")" binary="${destination}/${basename}.framework/${basename}" if ! [ -r "$binary" ]; then binary="${destination}/${basename}" fi # Strip invalid architectures so "fat" simulator / device frameworks work on device if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then strip_invalid_archs "$binary" fi # Resign the code if required by the build settings to avoid unstable apps code_sign_if_enabled "${destination}/$(basename "$1")" # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then local swift_runtime_libs swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]}) for lib in $swift_runtime_libs; do echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" code_sign_if_enabled "${destination}/${lib}" done fi } # Signs a framework with the provided identity code_sign_if_enabled() { if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then # Use the current code_sign_identitiy echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" echo "/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} --preserve-metadata=identifier,entitlements \"$1\"" /usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} --preserve-metadata=identifier,entitlements "$1" fi } # Strip invalid architectures strip_invalid_archs() { binary="$1" # Get architectures for current file archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)" stripped="" for arch in $archs; do if ! [[ "${VALID_ARCHS}" == *"$arch"* ]]; then # Strip non-valid architectures in-place lipo -remove "$arch" -output "$binary" "$binary" || exit 1 stripped="$stripped $arch" fi done if [[ "$stripped" ]]; then echo "Stripped $binary of architectures:$stripped" fi } ================================================ FILE: Example/Pods/Target Support Files/Pods-MWPhotoBrowser_Tests/Pods-MWPhotoBrowser_Tests-resources.sh ================================================ #!/bin/sh set -e mkdir -p "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt > "$RESOURCES_TO_COPY" XCASSET_FILES=() realpath() { DIRECTORY="$(cd "${1%/*}" && pwd)" FILENAME="${1##*/}" echo "$DIRECTORY/$FILENAME" } install_resource() { case $1 in *.storyboard) echo "ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .storyboard`.storyboardc ${PODS_ROOT}/$1 --sdk ${SDKROOT}" ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .storyboard`.storyboardc" "${PODS_ROOT}/$1" --sdk "${SDKROOT}" ;; *.xib) echo "ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .xib`.nib ${PODS_ROOT}/$1 --sdk ${SDKROOT}" ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .xib`.nib" "${PODS_ROOT}/$1" --sdk "${SDKROOT}" ;; *.framework) echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" echo "rsync -av ${PODS_ROOT}/$1 ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" rsync -av "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" ;; *.xcdatamodel) echo "xcrun momc \"${PODS_ROOT}/$1\" \"${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1"`.mom\"" xcrun momc "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcdatamodel`.mom" ;; *.xcdatamodeld) echo "xcrun momc \"${PODS_ROOT}/$1\" \"${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcdatamodeld`.momd\"" xcrun momc "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcdatamodeld`.momd" ;; *.xcmappingmodel) echo "xcrun mapc \"${PODS_ROOT}/$1\" \"${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcmappingmodel`.cdm\"" xcrun mapc "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcmappingmodel`.cdm" ;; *.xcassets) ABSOLUTE_XCASSET_FILE=$(realpath "${PODS_ROOT}/$1") XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") ;; /*) echo "$1" echo "$1" >> "$RESOURCES_TO_COPY" ;; *) echo "${PODS_ROOT}/$1" echo "${PODS_ROOT}/$1" >> "$RESOURCES_TO_COPY" ;; esac } if [[ "$CONFIGURATION" == "Debug" ]]; then install_resource "${BUILT_PRODUCTS_DIR}/MWPhotoBrowser.bundle" fi if [[ "$CONFIGURATION" == "Release" ]]; then install_resource "${BUILT_PRODUCTS_DIR}/MWPhotoBrowser.bundle" fi mkdir -p "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" fi rm -f "$RESOURCES_TO_COPY" if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ] then case "${TARGETED_DEVICE_FAMILY}" in 1,2) TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" ;; 1) TARGET_DEVICE_ARGS="--target-device iphone" ;; 2) TARGET_DEVICE_ARGS="--target-device ipad" ;; *) TARGET_DEVICE_ARGS="--target-device mac" ;; esac # Find all other xcassets (this unfortunately includes those of path pods and other targets). OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) while read line; do if [[ $line != "`realpath $PODS_ROOT`*" ]]; then XCASSET_FILES+=("$line") fi done <<<"$OTHER_XCASSETS" printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${IPHONEOS_DEPLOYMENT_TARGET}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" fi ================================================ FILE: Example/Pods/Target Support Files/Pods-MWPhotoBrowser_Tests/Pods-MWPhotoBrowser_Tests.debug.xcconfig ================================================ FRAMEWORK_SEARCH_PATHS = $(inherited) $(inherited) $(DEVELOPER_FRAMEWORKS_DIR) "$(PLATFORM_DIR)/Developer/Library/Frameworks" "$(DEVELOPER_DIR)/Platforms/iPhoneSimulator.platform/Developer/Library/Frameworks" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/DACircularProgress" "${PODS_ROOT}/Headers/Public/Expecta" "${PODS_ROOT}/Headers/Public/Expecta+Snapshots" "${PODS_ROOT}/Headers/Public/FBSnapshotTestCase" "${PODS_ROOT}/Headers/Public/MBProgressHUD" "${PODS_ROOT}/Headers/Public/MWPhotoBrowser" "${PODS_ROOT}/Headers/Public/SDWebImage" "${PODS_ROOT}/Headers/Public/Specta" OTHER_CFLAGS = $(inherited) -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/DACircularProgress" -isystem "${PODS_ROOT}/Headers/Public/Expecta" -isystem "${PODS_ROOT}/Headers/Public/Expecta+Snapshots" -isystem "${PODS_ROOT}/Headers/Public/FBSnapshotTestCase" -isystem "${PODS_ROOT}/Headers/Public/MBProgressHUD" -isystem "${PODS_ROOT}/Headers/Public/MWPhotoBrowser" -isystem "${PODS_ROOT}/Headers/Public/SDWebImage" -isystem "${PODS_ROOT}/Headers/Public/Specta" OTHER_LDFLAGS = $(inherited) -ObjC -l"DACircularProgress" -l"Expecta" -l"Expecta+Snapshots" -l"FBSnapshotTestCase" -l"MBProgressHUD" -l"MWPhotoBrowser" -l"SDWebImage" -l"Specta" -framework "AssetsLibrary" -framework "CoreGraphics" -framework "Foundation" -framework "ImageIO" -framework "MediaPlayer" -framework "QuartzCore" -framework "XCTest" -weak_framework "Photos" PODS_ROOT = ${SRCROOT}/Pods ================================================ FILE: Example/Pods/Target Support Files/Pods-MWPhotoBrowser_Tests/Pods-MWPhotoBrowser_Tests.release.xcconfig ================================================ FRAMEWORK_SEARCH_PATHS = $(inherited) $(inherited) $(DEVELOPER_FRAMEWORKS_DIR) "$(PLATFORM_DIR)/Developer/Library/Frameworks" "$(DEVELOPER_DIR)/Platforms/iPhoneSimulator.platform/Developer/Library/Frameworks" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/DACircularProgress" "${PODS_ROOT}/Headers/Public/Expecta" "${PODS_ROOT}/Headers/Public/Expecta+Snapshots" "${PODS_ROOT}/Headers/Public/FBSnapshotTestCase" "${PODS_ROOT}/Headers/Public/MBProgressHUD" "${PODS_ROOT}/Headers/Public/MWPhotoBrowser" "${PODS_ROOT}/Headers/Public/SDWebImage" "${PODS_ROOT}/Headers/Public/Specta" OTHER_CFLAGS = $(inherited) -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/DACircularProgress" -isystem "${PODS_ROOT}/Headers/Public/Expecta" -isystem "${PODS_ROOT}/Headers/Public/Expecta+Snapshots" -isystem "${PODS_ROOT}/Headers/Public/FBSnapshotTestCase" -isystem "${PODS_ROOT}/Headers/Public/MBProgressHUD" -isystem "${PODS_ROOT}/Headers/Public/MWPhotoBrowser" -isystem "${PODS_ROOT}/Headers/Public/SDWebImage" -isystem "${PODS_ROOT}/Headers/Public/Specta" OTHER_LDFLAGS = $(inherited) -ObjC -l"DACircularProgress" -l"Expecta" -l"Expecta+Snapshots" -l"FBSnapshotTestCase" -l"MBProgressHUD" -l"MWPhotoBrowser" -l"SDWebImage" -l"Specta" -framework "AssetsLibrary" -framework "CoreGraphics" -framework "Foundation" -framework "ImageIO" -framework "MediaPlayer" -framework "QuartzCore" -framework "XCTest" -weak_framework "Photos" PODS_ROOT = ${SRCROOT}/Pods ================================================ FILE: Example/Pods/Target Support Files/SDWebImage/SDWebImage-dummy.m ================================================ #import @interface PodsDummy_SDWebImage : NSObject @end @implementation PodsDummy_SDWebImage @end ================================================ FILE: Example/Pods/Target Support Files/SDWebImage/SDWebImage-prefix.pch ================================================ #ifdef __OBJC__ #import #endif ================================================ FILE: Example/Pods/Target Support Files/SDWebImage/SDWebImage.xcconfig ================================================ GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/SDWebImage" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/DACircularProgress" "${PODS_ROOT}/Headers/Public/Expecta" "${PODS_ROOT}/Headers/Public/Expecta+Snapshots" "${PODS_ROOT}/Headers/Public/FBSnapshotTestCase" "${PODS_ROOT}/Headers/Public/MBProgressHUD" "${PODS_ROOT}/Headers/Public/MWPhotoBrowser" "${PODS_ROOT}/Headers/Public/SDWebImage" "${PODS_ROOT}/Headers/Public/Specta" OTHER_LDFLAGS = -framework "ImageIO" PODS_ROOT = ${SRCROOT} SKIP_INSTALL = YES ================================================ FILE: Example/Pods/Target Support Files/Specta/Specta-dummy.m ================================================ #import @interface PodsDummy_Specta : NSObject @end @implementation PodsDummy_Specta @end ================================================ FILE: Example/Pods/Target Support Files/Specta/Specta-prefix.pch ================================================ #ifdef __OBJC__ #import #endif ================================================ FILE: Example/Pods/Target Support Files/Specta/Specta.xcconfig ================================================ FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" $(inherited) $(DEVELOPER_FRAMEWORKS_DIR) "$(PLATFORM_DIR)/Developer/Library/Frameworks" "$(DEVELOPER_DIR)/Platforms/iPhoneSimulator.platform/Developer/Library/Frameworks" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/Specta" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/DACircularProgress" "${PODS_ROOT}/Headers/Public/Expecta" "${PODS_ROOT}/Headers/Public/Expecta+Snapshots" "${PODS_ROOT}/Headers/Public/FBSnapshotTestCase" "${PODS_ROOT}/Headers/Public/MBProgressHUD" "${PODS_ROOT}/Headers/Public/MWPhotoBrowser" "${PODS_ROOT}/Headers/Public/SDWebImage" "${PODS_ROOT}/Headers/Public/Specta" OTHER_LDFLAGS = -framework "Foundation" -framework "XCTest" PODS_ROOT = ${SRCROOT} SKIP_INSTALL = YES ================================================ FILE: Example/Tests/Tests-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 ================================================ FILE: Example/Tests/Tests-Prefix.pch ================================================ // The contents of this file are implicitly included at the beginning of every test case source file. #ifdef __OBJC__ @import Specta; @import Expecta; @import FBSnapshotTestCase; @import Expecta_Snapshots; #endif ================================================ FILE: Example/Tests/Tests.m ================================================ // // MWPhotoBrowserTests.m // MWPhotoBrowserTests // // Created by Michael Waterfall on 07/04/2015. // Copyright (c) 2015 Michael Waterfall. All rights reserved. // // https://github.com/Specta/Specta SpecBegin(InitialSpecs) describe(@"these will fail", ^{ it(@"can do maths", ^{ expect(1).to.equal(2); }); it(@"can read", ^{ expect(@"number").to.equal(@"string"); }); it(@"will wait for 10 seconds and fail", ^{ waitUntil(^(DoneCallback done) { }); }); }); describe(@"these will pass", ^{ it(@"can do maths", ^{ expect(1).beLessThan(23); }); it(@"can read", ^{ expect(@"team").toNot.contain(@"I"); }); it(@"will wait and succeed", ^{ waitUntil(^(DoneCallback done) { done(); }); }); }); SpecEnd ================================================ FILE: Example/Tests/en.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ ================================================ FILE: LICENSE ================================================ Copyright (c) 2010 Michael Waterfall Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: MWPhotoBrowser.podspec ================================================ Pod::Spec.new do |s| s.name = 'MWPhotoBrowser' s.version = '2.1.2' s.license = 'MIT' s.summary = 'A simple iOS photo and video browser with optional grid view, captions and selections.' s.description = <<-DESCRIPTION MWPhotoBrowser can display one or more images or videos by providing either UIImage objects, PHAsset objects, or URLs to library assets, web images/videos or local files. The photo browser handles the downloading and caching of photos from the web seamlessly. Photos can be zoomed and panned, and optional (customisable) captions can be displayed. DESCRIPTION s.screenshots = [ 'https://raw.github.com/mwaterfall/MWPhotoBrowser/master/Screenshots/MWPhotoBrowser1.png', 'https://raw.github.com/mwaterfall/MWPhotoBrowser/master/Screenshots/MWPhotoBrowser2.png', 'https://raw.github.com/mwaterfall/MWPhotoBrowser/master/Screenshots/MWPhotoBrowser3.png', 'https://raw.github.com/mwaterfall/MWPhotoBrowser/master/Screenshots/MWPhotoBrowser4.png', 'https://raw.github.com/mwaterfall/MWPhotoBrowser/master/Screenshots/MWPhotoBrowser5.png', 'https://raw.github.com/mwaterfall/MWPhotoBrowser/master/Screenshots/MWPhotoBrowser6.png' ] s.homepage = 'https://github.com/mwaterfall/MWPhotoBrowser' s.author = { 'Michael Waterfall' => 'michaelwaterfall@gmail.com' } s.social_media_url = 'https://twitter.com/mwaterfall' s.source = { :git => 'https://github.com/mwaterfall/MWPhotoBrowser.git', :tag => '2.1.2' } s.platform = :ios, '7.0' s.source_files = 'Pod/Classes/**/*' s.resource_bundles = { 'MWPhotoBrowser' => ['Pod/Assets/*.png'] } s.requires_arc = true s.frameworks = 'ImageIO', 'QuartzCore', 'AssetsLibrary', 'MediaPlayer' s.weak_frameworks = 'Photos' s.dependency 'MBProgressHUD', '~> 0.9' s.dependency 'DACircularProgress', '~> 2.3' # SDWebImage # 3.7.2 contains bugs downloading local files # https://github.com/rs/SDWebImage/issues/1109 s.dependency 'SDWebImage', '~> 3.7', '!= 3.7.2' end ================================================ FILE: Pod/Assets/.gitkeep ================================================ ================================================ FILE: Pod/Classes/.gitkeep ================================================ ================================================ FILE: Pod/Classes/MWCaptionView.h ================================================ // // MWCaptionView.h // MWPhotoBrowser // // Created by Michael Waterfall on 30/12/2011. // Copyright (c) 2011 __MyCompanyName__. All rights reserved. // #import #import "MWPhotoProtocol.h" @interface MWCaptionView : UIToolbar // Init - (id)initWithPhoto:(id)photo; // To create your own custom caption view, subclass this view // and override the following two methods (as well as any other // UIView methods that you see fit): // Override -setupCaption so setup your subviews and customise the appearance // of your custom caption // You can access the photo's data by accessing the _photo ivar // If you need more data per photo then simply subclass MWPhoto and return your // subclass to the photo browsers -photoBrowser:photoAtIndex: delegate method - (void)setupCaption; // Override -sizeThatFits: and return a CGSize specifying the height of your // custom caption view. With width property is ignored and the caption is displayed // the full width of the screen - (CGSize)sizeThatFits:(CGSize)size; @end ================================================ FILE: Pod/Classes/MWCaptionView.m ================================================ // // MWCaptionView.m // MWPhotoBrowser // // Created by Michael Waterfall on 30/12/2011. // Copyright (c) 2011 __MyCompanyName__. All rights reserved. // #import "MWCommon.h" #import "MWCaptionView.h" #import "MWPhoto.h" static const CGFloat labelPadding = 10; // Private @interface MWCaptionView () { id _photo; UILabel *_label; } @end @implementation MWCaptionView - (id)initWithPhoto:(id)photo { self = [super initWithFrame:CGRectMake(0, 0, 320, 44)]; // Random initial frame if (self) { self.userInteractionEnabled = NO; _photo = photo; self.barStyle = UIBarStyleBlackTranslucent; self.tintColor = nil; self.barTintColor = nil; self.barStyle = UIBarStyleBlackTranslucent; [self setBackgroundImage:nil forToolbarPosition:UIBarPositionAny barMetrics:UIBarMetricsDefault]; self.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleTopMargin|UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin; [self setupCaption]; } return self; } - (CGSize)sizeThatFits:(CGSize)size { CGFloat maxHeight = 9999; if (_label.numberOfLines > 0) maxHeight = _label.font.leading*_label.numberOfLines; CGSize textSize = [_label.text boundingRectWithSize:CGSizeMake(size.width - labelPadding*2, maxHeight) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:_label.font} context:nil].size; return CGSizeMake(size.width, textSize.height + labelPadding * 2); } - (void)setupCaption { _label = [[UILabel alloc] initWithFrame:CGRectIntegral(CGRectMake(labelPadding, 0, self.bounds.size.width-labelPadding*2, self.bounds.size.height))]; _label.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; _label.opaque = NO; _label.backgroundColor = [UIColor clearColor]; _label.textAlignment = NSTextAlignmentCenter; _label.lineBreakMode = NSLineBreakByWordWrapping; _label.numberOfLines = 0; _label.textColor = [UIColor whiteColor]; _label.font = [UIFont systemFontOfSize:17]; if ([_photo respondsToSelector:@selector(caption)]) { _label.text = [_photo caption] ? [_photo caption] : @" "; } [self addSubview:_label]; } @end ================================================ FILE: Pod/Classes/MWCommon.h ================================================ // // MWPreprocessor.h // MWPhotoBrowser // // Created by Michael Waterfall on 01/10/2013. // #define SYSTEM_VERSION_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame) #define SYSTEM_VERSION_GREATER_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending) #define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending) #define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending) #define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending) ================================================ FILE: Pod/Classes/MWGridCell.h ================================================ // // MWGridCell.h // MWPhotoBrowser // // Created by Michael Waterfall on 08/10/2013. // // #import #import "MWPhoto.h" #import "MWGridViewController.h" @interface MWGridCell : UICollectionViewCell {} @property (nonatomic, weak) MWGridViewController *gridController; @property (nonatomic) NSUInteger index; @property (nonatomic) id photo; @property (nonatomic) BOOL selectionMode; @property (nonatomic) BOOL isSelected; - (void)displayImage; @end ================================================ FILE: Pod/Classes/MWGridCell.m ================================================ // // MWGridCell.m // MWPhotoBrowser // // Created by Michael Waterfall on 08/10/2013. // // #import #import "MWGridCell.h" #import "MWCommon.h" #import "MWPhotoBrowserPrivate.h" #import "UIImage+MWPhotoBrowser.h" #define VIDEO_INDICATOR_PADDING 10 @interface MWGridCell () { UIImageView *_imageView; UIImageView *_videoIndicator; UIImageView *_loadingError; DACircularProgressView *_loadingIndicator; UIButton *_selectedButton; } @end @implementation MWGridCell - (id)initWithFrame:(CGRect)frame { if ((self = [super initWithFrame:frame])) { // Grey background self.backgroundColor = [UIColor colorWithWhite:0.12 alpha:1]; // Image _imageView = [UIImageView new]; _imageView.frame = self.bounds; _imageView.contentMode = UIViewContentModeScaleAspectFill; _imageView.clipsToBounds = YES; _imageView.autoresizesSubviews = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; [self addSubview:_imageView]; // Video Image _videoIndicator = [UIImageView new]; _videoIndicator.hidden = NO; UIImage *videoIndicatorImage = [UIImage imageForResourcePath:@"MWPhotoBrowser.bundle/VideoOverlay" ofType:@"png" inBundle:[NSBundle bundleForClass:[self class]]]; _videoIndicator.frame = CGRectMake(self.bounds.size.width - videoIndicatorImage.size.width - VIDEO_INDICATOR_PADDING, self.bounds.size.height - videoIndicatorImage.size.height - VIDEO_INDICATOR_PADDING, videoIndicatorImage.size.width, videoIndicatorImage.size.height); _videoIndicator.image = videoIndicatorImage; _videoIndicator.autoresizesSubviews = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin; [self addSubview:_videoIndicator]; // Selection button _selectedButton = [UIButton buttonWithType:UIButtonTypeCustom]; _selectedButton.contentMode = UIViewContentModeTopRight; _selectedButton.adjustsImageWhenHighlighted = NO; [_selectedButton setImage:[UIImage imageForResourcePath:@"MWPhotoBrowser.bundle/ImageSelectedSmallOff" ofType:@"png" inBundle:[NSBundle bundleForClass:[self class]]] forState:UIControlStateNormal]; [_selectedButton setImage:[UIImage imageForResourcePath:@"MWPhotoBrowser.bundle/ImageSelectedSmallOn" ofType:@"png" inBundle:[NSBundle bundleForClass:[self class]]] forState:UIControlStateSelected]; [_selectedButton addTarget:self action:@selector(selectionButtonPressed) forControlEvents:UIControlEventTouchDown]; _selectedButton.hidden = YES; _selectedButton.frame = CGRectMake(0, 0, 44, 44); [self addSubview:_selectedButton]; // Loading indicator _loadingIndicator = [[DACircularProgressView alloc] initWithFrame:CGRectMake(0, 0, 40.0f, 40.0f)]; _loadingIndicator.userInteractionEnabled = NO; _loadingIndicator.thicknessRatio = 0.1; _loadingIndicator.roundedCorners = NO; [self addSubview:_loadingIndicator]; // Listen for photo loading notifications [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setProgressFromNotification:) name:MWPHOTO_PROGRESS_NOTIFICATION object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMWPhotoLoadingDidEndNotification:) name:MWPHOTO_LOADING_DID_END_NOTIFICATION object:nil]; } return self; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)setGridController:(MWGridViewController *)gridController { _gridController = gridController; // Set custom selection image if required if (_gridController.browser.customImageSelectedSmallIconName) { [_selectedButton setImage:[UIImage imageNamed:_gridController.browser.customImageSelectedSmallIconName] forState:UIControlStateSelected]; } } #pragma mark - View - (void)layoutSubviews { [super layoutSubviews]; _imageView.frame = self.bounds; _loadingIndicator.frame = CGRectMake(floorf((self.bounds.size.width - _loadingIndicator.frame.size.width) / 2.), floorf((self.bounds.size.height - _loadingIndicator.frame.size.height) / 2), _loadingIndicator.frame.size.width, _loadingIndicator.frame.size.height); _selectedButton.frame = CGRectMake(self.bounds.size.width - _selectedButton.frame.size.width - 0, 0, _selectedButton.frame.size.width, _selectedButton.frame.size.height); } #pragma mark - Cell - (void)prepareForReuse { _photo = nil; _gridController = nil; _imageView.image = nil; _loadingIndicator.progress = 0; _selectedButton.hidden = YES; [self hideImageFailure]; [super prepareForReuse]; } #pragma mark - Image Handling - (void)setPhoto:(id )photo { _photo = photo; if ([photo respondsToSelector:@selector(isVideo)]) { _videoIndicator.hidden = !photo.isVideo; } else { _videoIndicator.hidden = YES; } if (_photo) { if (![_photo underlyingImage]) { [self showLoadingIndicator]; } else { [self hideLoadingIndicator]; } } else { [self showImageFailure]; } } - (void)displayImage { _imageView.image = [_photo underlyingImage]; _selectedButton.hidden = !_selectionMode; [self hideImageFailure]; } #pragma mark - Selection - (void)setSelectionMode:(BOOL)selectionMode { _selectionMode = selectionMode; } - (void)setIsSelected:(BOOL)isSelected { _isSelected = isSelected; _selectedButton.selected = isSelected; } - (void)selectionButtonPressed { _selectedButton.selected = !_selectedButton.selected; [_gridController.browser setPhotoSelected:_selectedButton.selected atIndex:_index]; } #pragma mark - Touches - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { _imageView.alpha = 0.6; [super touchesBegan:touches withEvent:event]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { _imageView.alpha = 1; [super touchesEnded:touches withEvent:event]; } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { _imageView.alpha = 1; [super touchesCancelled:touches withEvent:event]; } #pragma mark Indicators - (void)hideLoadingIndicator { _loadingIndicator.hidden = YES; } - (void)showLoadingIndicator { _loadingIndicator.progress = 0; _loadingIndicator.hidden = NO; [self hideImageFailure]; } - (void)showImageFailure { // Only show if image is not empty if (![_photo respondsToSelector:@selector(emptyImage)] || !_photo.emptyImage) { if (!_loadingError) { _loadingError = [UIImageView new]; _loadingError.image = [UIImage imageForResourcePath:@"MWPhotoBrowser.bundle/ImageError" ofType:@"png" inBundle:[NSBundle bundleForClass:[self class]]]; _loadingError.userInteractionEnabled = NO; [_loadingError sizeToFit]; [self addSubview:_loadingError]; } _loadingError.frame = CGRectMake(floorf((self.bounds.size.width - _loadingError.frame.size.width) / 2.), floorf((self.bounds.size.height - _loadingError.frame.size.height) / 2), _loadingError.frame.size.width, _loadingError.frame.size.height); } [self hideLoadingIndicator]; _imageView.image = nil; } - (void)hideImageFailure { if (_loadingError) { [_loadingError removeFromSuperview]; _loadingError = nil; } } #pragma mark - Notifications - (void)setProgressFromNotification:(NSNotification *)notification { dispatch_async(dispatch_get_main_queue(), ^{ NSDictionary *dict = [notification object]; id photoWithProgress = [dict objectForKey:@"photo"]; if (photoWithProgress == _photo) { // NSLog(@"%f", [[dict valueForKey:@"progress"] floatValue]); float progress = [[dict valueForKey:@"progress"] floatValue]; _loadingIndicator.progress = MAX(MIN(1, progress), 0); } }); } - (void)handleMWPhotoLoadingDidEndNotification:(NSNotification *)notification { id photo = [notification object]; if (photo == _photo) { if ([photo underlyingImage]) { // Successful load [self displayImage]; } else { // Failed to load [self showImageFailure]; } [self hideLoadingIndicator]; } } @end ================================================ FILE: Pod/Classes/MWGridViewController.h ================================================ // // MWGridViewController.h // MWPhotoBrowser // // Created by Michael Waterfall on 08/10/2013. // // #import #import "MWPhotoBrowser.h" @interface MWGridViewController : UICollectionViewController {} @property (nonatomic, assign) MWPhotoBrowser *browser; @property (nonatomic) BOOL selectionMode; @property (nonatomic) CGPoint initialContentOffset; - (void)adjustOffsetsAsRequired; @end ================================================ FILE: Pod/Classes/MWGridViewController.m ================================================ // // MWGridViewController.m // MWPhotoBrowser // // Created by Michael Waterfall on 08/10/2013. // // #import "MWGridViewController.h" #import "MWGridCell.h" #import "MWPhotoBrowserPrivate.h" #import "MWCommon.h" @interface MWGridViewController () { // Store margins for current setup CGFloat _margin, _gutter, _marginL, _gutterL, _columns, _columnsL; } @end @implementation MWGridViewController - (id)init { if ((self = [super initWithCollectionViewLayout:[UICollectionViewFlowLayout new]])) { // Defaults _columns = 3, _columnsL = 4; _margin = 0, _gutter = 1; _marginL = 0, _gutterL = 1; // For pixel perfection... if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { // iPad _columns = 6, _columnsL = 8; _margin = 1, _gutter = 2; _marginL = 1, _gutterL = 2; } else if ([UIScreen mainScreen].bounds.size.height == 480) { // iPhone 3.5 inch _columns = 3, _columnsL = 4; _margin = 0, _gutter = 1; _marginL = 1, _gutterL = 2; } else { // iPhone 4 inch _columns = 3, _columnsL = 5; _margin = 0, _gutter = 1; _marginL = 0, _gutterL = 2; } _initialContentOffset = CGPointMake(0, CGFLOAT_MAX); } return self; } #pragma mark - View - (void)viewDidLoad { [super viewDidLoad]; [self.collectionView registerClass:[MWGridCell class] forCellWithReuseIdentifier:@"GridCell"]; self.collectionView.alwaysBounceVertical = YES; self.collectionView.backgroundColor = [UIColor blackColor]; } - (void)viewWillDisappear:(BOOL)animated { // Cancel outstanding loading NSArray *visibleCells = [self.collectionView visibleCells]; if (visibleCells) { for (MWGridCell *cell in visibleCells) { [cell.photo cancelAnyLoading]; } } [super viewWillDisappear:animated]; } - (void)viewWillLayoutSubviews { [super viewWillLayoutSubviews]; [self performLayout]; } - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; } - (void)adjustOffsetsAsRequired { // Move to previous content offset if (_initialContentOffset.y != CGFLOAT_MAX) { self.collectionView.contentOffset = _initialContentOffset; [self.collectionView layoutIfNeeded]; // Layout after content offset change } // Check if current item is visible and if not, make it so! if (_browser.numberOfPhotos > 0) { NSIndexPath *currentPhotoIndexPath = [NSIndexPath indexPathForItem:_browser.currentIndex inSection:0]; NSArray *visibleIndexPaths = [self.collectionView indexPathsForVisibleItems]; BOOL currentVisible = NO; for (NSIndexPath *indexPath in visibleIndexPaths) { if ([indexPath isEqual:currentPhotoIndexPath]) { currentVisible = YES; break; } } if (!currentVisible) { [self.collectionView scrollToItemAtIndexPath:currentPhotoIndexPath atScrollPosition:UICollectionViewScrollPositionNone animated:NO]; } } } - (void)performLayout { UINavigationBar *navBar = self.navigationController.navigationBar; self.collectionView.contentInset = UIEdgeInsetsMake(navBar.frame.origin.y + navBar.frame.size.height + [self getGutter], 0, 0, 0); } - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { [self.collectionView reloadData]; [self performLayout]; // needed for iOS 5 & 6 } #pragma mark - Layout - (CGFloat)getColumns { if ((UIInterfaceOrientationIsPortrait(self.interfaceOrientation))) { return _columns; } else { return _columnsL; } } - (CGFloat)getMargin { if ((UIInterfaceOrientationIsPortrait(self.interfaceOrientation))) { return _margin; } else { return _marginL; } } - (CGFloat)getGutter { if ((UIInterfaceOrientationIsPortrait(self.interfaceOrientation))) { return _gutter; } else { return _gutterL; } } #pragma mark - Collection View - (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section { return [_browser numberOfPhotos]; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { MWGridCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"GridCell" forIndexPath:indexPath]; if (!cell) { cell = [[MWGridCell alloc] init]; } id photo = [_browser thumbPhotoAtIndex:indexPath.row]; cell.photo = photo; cell.gridController = self; cell.selectionMode = _selectionMode; cell.isSelected = [_browser photoIsSelectedAtIndex:indexPath.row]; cell.index = indexPath.row; UIImage *img = [_browser imageForPhoto:photo]; if (img) { [cell displayImage]; } else { [photo loadUnderlyingImageAndNotify]; } return cell; } - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { [_browser setCurrentPhotoIndex:indexPath.row]; [_browser hideGrid]; } - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { [((MWGridCell *)cell).photo cancelAnyLoading]; } - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { CGFloat margin = [self getMargin]; CGFloat gutter = [self getGutter]; CGFloat columns = [self getColumns]; CGFloat value = floorf(((self.view.bounds.size.width - (columns - 1) * gutter - 2 * margin) / columns)); return CGSizeMake(value, value); } - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section { return [self getGutter]; } - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { return [self getGutter]; } - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { CGFloat margin = [self getMargin]; return UIEdgeInsetsMake(margin, margin, margin, margin); } @end ================================================ FILE: Pod/Classes/MWPhoto.h ================================================ // // MWPhoto.h // MWPhotoBrowser // // Created by Michael Waterfall on 17/10/2010. // Copyright 2010 d3i. All rights reserved. // #import #import #import "MWPhotoProtocol.h" // This class models a photo/image and it's caption // If you want to handle photos, caching, decompression // yourself then you can simply ensure your custom data model // conforms to MWPhotoProtocol @interface MWPhoto : NSObject @property (nonatomic, strong) NSString *caption; @property (nonatomic, strong) NSURL *videoURL; @property (nonatomic) BOOL emptyImage; @property (nonatomic) BOOL isVideo; + (MWPhoto *)photoWithImage:(UIImage *)image; + (MWPhoto *)photoWithURL:(NSURL *)url; + (MWPhoto *)photoWithAsset:(PHAsset *)asset targetSize:(CGSize)targetSize; + (MWPhoto *)videoWithURL:(NSURL *)url; // Initialise video with no poster image - (id)init; - (id)initWithImage:(UIImage *)image; - (id)initWithURL:(NSURL *)url; - (id)initWithAsset:(PHAsset *)asset targetSize:(CGSize)targetSize; - (id)initWithVideoURL:(NSURL *)url; @end ================================================ FILE: Pod/Classes/MWPhoto.m ================================================ // // MWPhoto.m // MWPhotoBrowser // // Created by Michael Waterfall on 17/10/2010. // Copyright 2010 d3i. All rights reserved. // #import #import #import #import #import "MWPhoto.h" #import "MWPhotoBrowser.h" @interface MWPhoto () { BOOL _loadingInProgress; id _webImageOperation; PHImageRequestID _assetRequestID; PHImageRequestID _assetVideoRequestID; } @property (nonatomic, strong) UIImage *image; @property (nonatomic, strong) NSURL *photoURL; @property (nonatomic, strong) PHAsset *asset; @property (nonatomic) CGSize assetTargetSize; - (void)imageLoadingComplete; @end @implementation MWPhoto @synthesize underlyingImage = _underlyingImage; // synth property from protocol #pragma mark - Class Methods + (MWPhoto *)photoWithImage:(UIImage *)image { return [[MWPhoto alloc] initWithImage:image]; } + (MWPhoto *)photoWithURL:(NSURL *)url { return [[MWPhoto alloc] initWithURL:url]; } + (MWPhoto *)photoWithAsset:(PHAsset *)asset targetSize:(CGSize)targetSize { return [[MWPhoto alloc] initWithAsset:asset targetSize:targetSize]; } + (MWPhoto *)videoWithURL:(NSURL *)url { return [[MWPhoto alloc] initWithVideoURL:url]; } #pragma mark - Init - (id)init { if ((self = [super init])) { self.emptyImage = YES; [self setup]; } return self; } - (id)initWithImage:(UIImage *)image { if ((self = [super init])) { self.image = image; [self setup]; } return self; } - (id)initWithURL:(NSURL *)url { if ((self = [super init])) { self.photoURL = url; [self setup]; } return self; } - (id)initWithAsset:(PHAsset *)asset targetSize:(CGSize)targetSize { if ((self = [super init])) { self.asset = asset; self.assetTargetSize = targetSize; self.isVideo = asset.mediaType == PHAssetMediaTypeVideo; [self setup]; } return self; } - (id)initWithVideoURL:(NSURL *)url { if ((self = [super init])) { self.videoURL = url; self.isVideo = YES; self.emptyImage = YES; [self setup]; } return self; } - (void)setup { _assetRequestID = PHInvalidImageRequestID; _assetVideoRequestID = PHInvalidImageRequestID; } - (void)dealloc { [self cancelAnyLoading]; } #pragma mark - Video - (void)setVideoURL:(NSURL *)videoURL { _videoURL = videoURL; self.isVideo = YES; } - (void)getVideoURL:(void (^)(NSURL *url))completion { if (_videoURL) { completion(_videoURL); } else if (_asset && _asset.mediaType == PHAssetMediaTypeVideo) { [self cancelVideoRequest]; // Cancel any existing PHVideoRequestOptions *options = [PHVideoRequestOptions new]; options.networkAccessAllowed = YES; typeof(self) __weak weakSelf = self; _assetVideoRequestID = [[PHImageManager defaultManager] requestAVAssetForVideo:_asset options:options resultHandler:^(AVAsset *asset, AVAudioMix *audioMix, NSDictionary *info) { // dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ // Testing typeof(self) strongSelf = weakSelf; if (!strongSelf) return; strongSelf->_assetVideoRequestID = PHInvalidImageRequestID; if ([asset isKindOfClass:[AVURLAsset class]]) { completion(((AVURLAsset *)asset).URL); } else { completion(nil); } }]; } } #pragma mark - MWPhoto Protocol Methods - (UIImage *)underlyingImage { return _underlyingImage; } - (void)loadUnderlyingImageAndNotify { NSAssert([[NSThread currentThread] isMainThread], @"This method must be called on the main thread."); if (_loadingInProgress) return; _loadingInProgress = YES; @try { if (self.underlyingImage) { [self imageLoadingComplete]; } else { [self performLoadUnderlyingImageAndNotify]; } } @catch (NSException *exception) { self.underlyingImage = nil; _loadingInProgress = NO; [self imageLoadingComplete]; } @finally { } } // Set the underlyingImage - (void)performLoadUnderlyingImageAndNotify { // Get underlying image if (_image) { // We have UIImage! self.underlyingImage = _image; [self imageLoadingComplete]; } else if (_photoURL) { // Check what type of url it is if ([[[_photoURL scheme] lowercaseString] isEqualToString:@"assets-library"]) { // Load from assets library [self _performLoadUnderlyingImageAndNotifyWithAssetsLibraryURL: _photoURL]; } else if ([_photoURL isFileReferenceURL]) { // Load from local file async [self _performLoadUnderlyingImageAndNotifyWithLocalFileURL: _photoURL]; } else { // Load async from web (using SDWebImage) [self _performLoadUnderlyingImageAndNotifyWithWebURL: _photoURL]; } } else if (_asset) { // Load from photos asset [self _performLoadUnderlyingImageAndNotifyWithAsset: _asset targetSize:_assetTargetSize]; } else { // Image is empty [self imageLoadingComplete]; } } // Load from local file - (void)_performLoadUnderlyingImageAndNotifyWithWebURL:(NSURL *)url { @try { SDWebImageManager *manager = [SDWebImageManager sharedManager]; _webImageOperation = [manager downloadImageWithURL:url options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) { if (expectedSize > 0) { float progress = receivedSize / (float)expectedSize; NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithFloat:progress], @"progress", self, @"photo", nil]; [[NSNotificationCenter defaultCenter] postNotificationName:MWPHOTO_PROGRESS_NOTIFICATION object:dict]; } } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { if (error) { MWLog(@"SDWebImage failed to download image: %@", error); } _webImageOperation = nil; self.underlyingImage = image; dispatch_async(dispatch_get_main_queue(), ^{ [self imageLoadingComplete]; }); }]; } @catch (NSException *e) { MWLog(@"Photo from web: %@", e); _webImageOperation = nil; [self imageLoadingComplete]; } } // Load from local file - (void)_performLoadUnderlyingImageAndNotifyWithLocalFileURL:(NSURL *)url { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @autoreleasepool { @try { self.underlyingImage = [UIImage imageWithContentsOfFile:url.path]; if (!_underlyingImage) { MWLog(@"Error loading photo from path: %@", url.path); } } @finally { [self performSelectorOnMainThread:@selector(imageLoadingComplete) withObject:nil waitUntilDone:NO]; } } }); } // Load from asset library async - (void)_performLoadUnderlyingImageAndNotifyWithAssetsLibraryURL:(NSURL *)url { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @autoreleasepool { @try { ALAssetsLibrary *assetslibrary = [[ALAssetsLibrary alloc] init]; [assetslibrary assetForURL:url resultBlock:^(ALAsset *asset){ ALAssetRepresentation *rep = [asset defaultRepresentation]; CGImageRef iref = [rep fullScreenImage]; if (iref) { self.underlyingImage = [UIImage imageWithCGImage:iref]; } [self performSelectorOnMainThread:@selector(imageLoadingComplete) withObject:nil waitUntilDone:NO]; } failureBlock:^(NSError *error) { self.underlyingImage = nil; MWLog(@"Photo from asset library error: %@",error); [self performSelectorOnMainThread:@selector(imageLoadingComplete) withObject:nil waitUntilDone:NO]; }]; } @catch (NSException *e) { MWLog(@"Photo from asset library error: %@", e); [self performSelectorOnMainThread:@selector(imageLoadingComplete) withObject:nil waitUntilDone:NO]; } } }); } // Load from photos library - (void)_performLoadUnderlyingImageAndNotifyWithAsset:(PHAsset *)asset targetSize:(CGSize)targetSize { PHImageManager *imageManager = [PHImageManager defaultManager]; PHImageRequestOptions *options = [PHImageRequestOptions new]; options.networkAccessAllowed = YES; options.resizeMode = PHImageRequestOptionsResizeModeFast; options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat; options.synchronous = false; options.progressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary *info) { NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithDouble: progress], @"progress", self, @"photo", nil]; [[NSNotificationCenter defaultCenter] postNotificationName:MWPHOTO_PROGRESS_NOTIFICATION object:dict]; }; _assetRequestID = [imageManager requestImageForAsset:asset targetSize:targetSize contentMode:PHImageContentModeAspectFit options:options resultHandler:^(UIImage *result, NSDictionary *info) { dispatch_async(dispatch_get_main_queue(), ^{ self.underlyingImage = result; [self imageLoadingComplete]; }); }]; } // Release if we can get it again from path or url - (void)unloadUnderlyingImage { _loadingInProgress = NO; self.underlyingImage = nil; } - (void)imageLoadingComplete { NSAssert([[NSThread currentThread] isMainThread], @"This method must be called on the main thread."); // Complete so notify _loadingInProgress = NO; // Notify on next run loop [self performSelector:@selector(postCompleteNotification) withObject:nil afterDelay:0]; } - (void)postCompleteNotification { [[NSNotificationCenter defaultCenter] postNotificationName:MWPHOTO_LOADING_DID_END_NOTIFICATION object:self]; } - (void)cancelAnyLoading { if (_webImageOperation != nil) { [_webImageOperation cancel]; _loadingInProgress = NO; } [self cancelImageRequest]; [self cancelVideoRequest]; } - (void)cancelImageRequest { if (_assetRequestID != PHInvalidImageRequestID) { [[PHImageManager defaultManager] cancelImageRequest:_assetRequestID]; _assetRequestID = PHInvalidImageRequestID; } } - (void)cancelVideoRequest { if (_assetVideoRequestID != PHInvalidImageRequestID) { [[PHImageManager defaultManager] cancelImageRequest:_assetVideoRequestID]; _assetVideoRequestID = PHInvalidImageRequestID; } } @end ================================================ FILE: Pod/Classes/MWPhotoBrowser.h ================================================ // // MWPhotoBrowser.h // MWPhotoBrowser // // Created by Michael Waterfall on 14/10/2010. // Copyright 2010 d3i. All rights reserved. // #import #import "MWPhoto.h" #import "MWPhotoProtocol.h" #import "MWCaptionView.h" // Debug Logging #if 0 // Set to 1 to enable debug logging #define MWLog(x, ...) NSLog(x, ## __VA_ARGS__); #else #define MWLog(x, ...) #endif @class MWPhotoBrowser; @protocol MWPhotoBrowserDelegate - (NSUInteger)numberOfPhotosInPhotoBrowser:(MWPhotoBrowser *)photoBrowser; - (id )photoBrowser:(MWPhotoBrowser *)photoBrowser photoAtIndex:(NSUInteger)index; @optional - (id )photoBrowser:(MWPhotoBrowser *)photoBrowser thumbPhotoAtIndex:(NSUInteger)index; - (MWCaptionView *)photoBrowser:(MWPhotoBrowser *)photoBrowser captionViewForPhotoAtIndex:(NSUInteger)index; - (NSString *)photoBrowser:(MWPhotoBrowser *)photoBrowser titleForPhotoAtIndex:(NSUInteger)index; - (void)photoBrowser:(MWPhotoBrowser *)photoBrowser didDisplayPhotoAtIndex:(NSUInteger)index; - (void)photoBrowser:(MWPhotoBrowser *)photoBrowser actionButtonPressedForPhotoAtIndex:(NSUInteger)index; - (BOOL)photoBrowser:(MWPhotoBrowser *)photoBrowser isPhotoSelectedAtIndex:(NSUInteger)index; - (void)photoBrowser:(MWPhotoBrowser *)photoBrowser photoAtIndex:(NSUInteger)index selectedChanged:(BOOL)selected; - (void)photoBrowserDidFinishModalPresentation:(MWPhotoBrowser *)photoBrowser; @end @interface MWPhotoBrowser : UIViewController @property (nonatomic, weak) IBOutlet id delegate; @property (nonatomic) BOOL zoomPhotosToFill; @property (nonatomic) BOOL displayNavArrows; @property (nonatomic) BOOL displayActionButton; @property (nonatomic) BOOL displaySelectionButtons; @property (nonatomic) BOOL alwaysShowControls; @property (nonatomic) BOOL enableGrid; @property (nonatomic) BOOL enableSwipeToDismiss; @property (nonatomic) BOOL startOnGrid; @property (nonatomic) BOOL autoPlayOnAppear; @property (nonatomic) NSUInteger delayToHideElements; @property (nonatomic, readonly) NSUInteger currentIndex; // Customise image selection icons as they are the only icons with a colour tint // Icon should be located in the app's main bundle @property (nonatomic, strong) NSString *customImageSelectedIconName; @property (nonatomic, strong) NSString *customImageSelectedSmallIconName; // Init - (id)initWithPhotos:(NSArray *)photosArray; - (id)initWithDelegate:(id )delegate; // Reloads the photo browser and refetches data - (void)reloadData; // Set page that photo browser starts on - (void)setCurrentPhotoIndex:(NSUInteger)index; // Navigation - (void)showNextPhotoAnimated:(BOOL)animated; - (void)showPreviousPhotoAnimated:(BOOL)animated; @end ================================================ FILE: Pod/Classes/MWPhotoBrowser.m ================================================ // // MWPhotoBrowser.m // MWPhotoBrowser // // Created by Michael Waterfall on 14/10/2010. // Copyright 2010 d3i. All rights reserved. // #import #import "MWCommon.h" #import "MWPhotoBrowser.h" #import "MWPhotoBrowserPrivate.h" #import "SDImageCache.h" #import "UIImage+MWPhotoBrowser.h" #define PADDING 10 static void * MWVideoPlayerObservation = &MWVideoPlayerObservation; @implementation MWPhotoBrowser #pragma mark - Init - (id)init { if ((self = [super init])) { [self _initialisation]; } return self; } - (id)initWithDelegate:(id )delegate { if ((self = [self init])) { _delegate = delegate; } return self; } - (id)initWithPhotos:(NSArray *)photosArray { if ((self = [self init])) { _fixedPhotosArray = photosArray; } return self; } - (id)initWithCoder:(NSCoder *)decoder { if ((self = [super initWithCoder:decoder])) { [self _initialisation]; } return self; } - (void)_initialisation { // Defaults NSNumber *isVCBasedStatusBarAppearanceNum = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIViewControllerBasedStatusBarAppearance"]; if (isVCBasedStatusBarAppearanceNum) { _isVCBasedStatusBarAppearance = isVCBasedStatusBarAppearanceNum.boolValue; } else { _isVCBasedStatusBarAppearance = YES; // default } self.hidesBottomBarWhenPushed = YES; _hasBelongedToViewController = NO; _photoCount = NSNotFound; _previousLayoutBounds = CGRectZero; _currentPageIndex = 0; _previousPageIndex = NSUIntegerMax; _currentVideoIndex = NSUIntegerMax; _displayActionButton = YES; _displayNavArrows = NO; _zoomPhotosToFill = YES; _performingLayout = NO; // Reset on view did appear _rotating = NO; _viewIsActive = NO; _enableGrid = YES; _startOnGrid = NO; _enableSwipeToDismiss = YES; _delayToHideElements = 5; _visiblePages = [[NSMutableSet alloc] init]; _recycledPages = [[NSMutableSet alloc] init]; _photos = [[NSMutableArray alloc] init]; _thumbPhotos = [[NSMutableArray alloc] init]; _currentGridContentOffset = CGPointMake(0, CGFLOAT_MAX); _didSavePreviousStateOfNavBar = NO; self.automaticallyAdjustsScrollViewInsets = NO; // Listen for MWPhoto notifications [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMWPhotoLoadingDidEndNotification:) name:MWPHOTO_LOADING_DID_END_NOTIFICATION object:nil]; } - (void)dealloc { [self clearCurrentVideo]; _pagingScrollView.delegate = nil; [[NSNotificationCenter defaultCenter] removeObserver:self]; [self releaseAllUnderlyingPhotos:NO]; [[SDImageCache sharedImageCache] clearMemory]; // clear memory } - (void)releaseAllUnderlyingPhotos:(BOOL)preserveCurrent { // Create a copy in case this array is modified while we are looping through // Release photos NSArray *copy = [_photos copy]; for (id p in copy) { if (p != [NSNull null]) { if (preserveCurrent && p == [self photoAtIndex:self.currentIndex]) { continue; // skip current } [p unloadUnderlyingImage]; } } // Release thumbs copy = [_thumbPhotos copy]; for (id p in copy) { if (p != [NSNull null]) { [p unloadUnderlyingImage]; } } } - (void)didReceiveMemoryWarning { // Release any cached data, images, etc that aren't in use. [self releaseAllUnderlyingPhotos:YES]; [_recycledPages removeAllObjects]; // Releases the view if it doesn't have a superview. [super didReceiveMemoryWarning]; } #pragma mark - View Loading // Implement viewDidLoad to do additional setup after loading the view, typically from a nib. - (void)viewDidLoad { // Validate grid settings if (_startOnGrid) _enableGrid = YES; if (_enableGrid) { _enableGrid = [_delegate respondsToSelector:@selector(photoBrowser:thumbPhotoAtIndex:)]; } if (!_enableGrid) _startOnGrid = NO; // View self.view.backgroundColor = [UIColor blackColor]; self.view.clipsToBounds = YES; // Setup paging scrolling view CGRect pagingScrollViewFrame = [self frameForPagingScrollView]; _pagingScrollView = [[UIScrollView alloc] initWithFrame:pagingScrollViewFrame]; _pagingScrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; _pagingScrollView.pagingEnabled = YES; _pagingScrollView.delegate = self; _pagingScrollView.showsHorizontalScrollIndicator = NO; _pagingScrollView.showsVerticalScrollIndicator = NO; _pagingScrollView.backgroundColor = [UIColor blackColor]; _pagingScrollView.contentSize = [self contentSizeForPagingScrollView]; [self.view addSubview:_pagingScrollView]; // Toolbar _toolbar = [[UIToolbar alloc] initWithFrame:[self frameForToolbarAtOrientation:self.interfaceOrientation]]; _toolbar.tintColor = [UIColor whiteColor]; _toolbar.barTintColor = nil; [_toolbar setBackgroundImage:nil forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault]; [_toolbar setBackgroundImage:nil forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsLandscapePhone]; _toolbar.barStyle = UIBarStyleBlackTranslucent; _toolbar.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth; // Toolbar Items if (self.displayNavArrows) { NSString *arrowPathFormat = @"MWPhotoBrowser.bundle/UIBarButtonItemArrow%@"; UIImage *previousButtonImage = [UIImage imageForResourcePath:[NSString stringWithFormat:arrowPathFormat, @"Left"] ofType:@"png" inBundle:[NSBundle bundleForClass:[self class]]]; UIImage *nextButtonImage = [UIImage imageForResourcePath:[NSString stringWithFormat:arrowPathFormat, @"Right"] ofType:@"png" inBundle:[NSBundle bundleForClass:[self class]]]; _previousButton = [[UIBarButtonItem alloc] initWithImage:previousButtonImage style:UIBarButtonItemStylePlain target:self action:@selector(gotoPreviousPage)]; _nextButton = [[UIBarButtonItem alloc] initWithImage:nextButtonImage style:UIBarButtonItemStylePlain target:self action:@selector(gotoNextPage)]; } if (self.displayActionButton) { _actionButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector(actionButtonPressed:)]; } // Update [self reloadData]; // Swipe to dismiss if (_enableSwipeToDismiss) { UISwipeGestureRecognizer *swipeGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(doneButtonPressed:)]; swipeGesture.direction = UISwipeGestureRecognizerDirectionDown | UISwipeGestureRecognizerDirectionUp; [self.view addGestureRecognizer:swipeGesture]; } // Super [super viewDidLoad]; } - (void)performLayout { // Setup _performingLayout = YES; NSUInteger numberOfPhotos = [self numberOfPhotos]; // Setup pages [_visiblePages removeAllObjects]; [_recycledPages removeAllObjects]; // Navigation buttons if ([self.navigationController.viewControllers objectAtIndex:0] == self) { // We're first on stack so show done button _doneButton = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Done", nil) style:UIBarButtonItemStylePlain target:self action:@selector(doneButtonPressed:)]; // Set appearance [_doneButton setBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsDefault]; [_doneButton setBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone]; [_doneButton setBackgroundImage:nil forState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault]; [_doneButton setBackgroundImage:nil forState:UIControlStateHighlighted barMetrics:UIBarMetricsLandscapePhone]; [_doneButton setTitleTextAttributes:[NSDictionary dictionary] forState:UIControlStateNormal]; [_doneButton setTitleTextAttributes:[NSDictionary dictionary] forState:UIControlStateHighlighted]; self.navigationItem.rightBarButtonItem = _doneButton; } else { // We're not first so show back button UIViewController *previousViewController = [self.navigationController.viewControllers objectAtIndex:self.navigationController.viewControllers.count-2]; NSString *backButtonTitle = previousViewController.navigationItem.backBarButtonItem ? previousViewController.navigationItem.backBarButtonItem.title : previousViewController.title; UIBarButtonItem *newBackButton = [[UIBarButtonItem alloc] initWithTitle:backButtonTitle style:UIBarButtonItemStylePlain target:nil action:nil]; // Appearance [newBackButton setBackButtonBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsDefault]; [newBackButton setBackButtonBackgroundImage:nil forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone]; [newBackButton setBackButtonBackgroundImage:nil forState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault]; [newBackButton setBackButtonBackgroundImage:nil forState:UIControlStateHighlighted barMetrics:UIBarMetricsLandscapePhone]; [newBackButton setTitleTextAttributes:[NSDictionary dictionary] forState:UIControlStateNormal]; [newBackButton setTitleTextAttributes:[NSDictionary dictionary] forState:UIControlStateHighlighted]; _previousViewControllerBackButton = previousViewController.navigationItem.backBarButtonItem; // remember previous previousViewController.navigationItem.backBarButtonItem = newBackButton; } // Toolbar items BOOL hasItems = NO; UIBarButtonItem *fixedSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:self action:nil]; fixedSpace.width = 32; // To balance action button UIBarButtonItem *flexSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:self action:nil]; NSMutableArray *items = [[NSMutableArray alloc] init]; // Left button - Grid if (_enableGrid) { hasItems = YES; [items addObject:[[UIBarButtonItem alloc] initWithImage:[UIImage imageForResourcePath:@"MWPhotoBrowser.bundle/UIBarButtonItemGrid" ofType:@"png" inBundle:[NSBundle bundleForClass:[self class]]] style:UIBarButtonItemStylePlain target:self action:@selector(showGridAnimated)]]; } else { [items addObject:fixedSpace]; } // Middle - Nav if (_previousButton && _nextButton && numberOfPhotos > 1) { hasItems = YES; [items addObject:flexSpace]; [items addObject:_previousButton]; [items addObject:flexSpace]; [items addObject:_nextButton]; [items addObject:flexSpace]; } else { [items addObject:flexSpace]; } // Right - Action if (_actionButton && !(!hasItems && !self.navigationItem.rightBarButtonItem)) { [items addObject:_actionButton]; } else { // We're not showing the toolbar so try and show in top right if (_actionButton) self.navigationItem.rightBarButtonItem = _actionButton; [items addObject:fixedSpace]; } // Toolbar visibility [_toolbar setItems:items]; BOOL hideToolbar = YES; for (UIBarButtonItem* item in _toolbar.items) { if (item != fixedSpace && item != flexSpace) { hideToolbar = NO; break; } } if (hideToolbar) { [_toolbar removeFromSuperview]; } else { [self.view addSubview:_toolbar]; } // Update nav [self updateNavigation]; // Content offset _pagingScrollView.contentOffset = [self contentOffsetForPageAtIndex:_currentPageIndex]; [self tilePages]; _performingLayout = NO; } // Release any retained subviews of the main view. - (void)viewDidUnload { _currentPageIndex = 0; _pagingScrollView = nil; _visiblePages = nil; _recycledPages = nil; _toolbar = nil; _previousButton = nil; _nextButton = nil; _progressHUD = nil; [super viewDidUnload]; } - (BOOL)presentingViewControllerPrefersStatusBarHidden { UIViewController *presenting = self.presentingViewController; if (presenting) { if ([presenting isKindOfClass:[UINavigationController class]]) { presenting = [(UINavigationController *)presenting topViewController]; } } else { // We're in a navigation controller so get previous one! if (self.navigationController && self.navigationController.viewControllers.count > 1) { presenting = [self.navigationController.viewControllers objectAtIndex:self.navigationController.viewControllers.count-2]; } } if (presenting) { return [presenting prefersStatusBarHidden]; } else { return NO; } } #pragma mark - Appearance - (void)viewWillAppear:(BOOL)animated { // Super [super viewWillAppear:animated]; // Status bar if (!_viewHasAppearedInitially) { _leaveStatusBarAlone = [self presentingViewControllerPrefersStatusBarHidden]; // Check if status bar is hidden on first appear, and if so then ignore it if (CGRectEqualToRect([[UIApplication sharedApplication] statusBarFrame], CGRectZero)) { _leaveStatusBarAlone = YES; } } // Set style if (!_leaveStatusBarAlone && UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { _previousStatusBarStyle = [[UIApplication sharedApplication] statusBarStyle]; [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent animated:animated]; } // Navigation bar appearance if (!_viewIsActive && [self.navigationController.viewControllers objectAtIndex:0] != self) { [self storePreviousNavBarAppearance]; } [self setNavBarAppearance:animated]; // Update UI [self hideControlsAfterDelay]; // Initial appearance if (!_viewHasAppearedInitially) { if (_startOnGrid) { [self showGrid:NO]; } } // If rotation occured while we're presenting a modal // and the index changed, make sure we show the right one now if (_currentPageIndex != _pageIndexBeforeRotation) { [self jumpToPageAtIndex:_pageIndexBeforeRotation animated:NO]; } // Layout [self.view setNeedsLayout]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; _viewIsActive = YES; // Autoplay if first is video if (!_viewHasAppearedInitially) { if (_autoPlayOnAppear) { MWPhoto *photo = [self photoAtIndex:_currentPageIndex]; if ([photo respondsToSelector:@selector(isVideo)] && photo.isVideo) { [self playVideoAtIndex:_currentPageIndex]; } } } _viewHasAppearedInitially = YES; } - (void)viewWillDisappear:(BOOL)animated { // Detect if rotation occurs while we're presenting a modal _pageIndexBeforeRotation = _currentPageIndex; // Check that we're disappearing for good // self.isMovingFromParentViewController just doesn't work, ever. Or self.isBeingDismissed if ((_doneButton && self.navigationController.isBeingDismissed) || ([self.navigationController.viewControllers objectAtIndex:0] != self && ![self.navigationController.viewControllers containsObject:self])) { // State _viewIsActive = NO; [self clearCurrentVideo]; // Clear current playing video // Bar state / appearance [self restorePreviousNavBarAppearance:animated]; } // Controls [self.navigationController.navigationBar.layer removeAllAnimations]; // Stop all animations on nav bar [NSObject cancelPreviousPerformRequestsWithTarget:self]; // Cancel any pending toggles from taps [self setControlsHidden:NO animated:NO permanent:YES]; // Status bar if (!_leaveStatusBarAlone && UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { [[UIApplication sharedApplication] setStatusBarStyle:_previousStatusBarStyle animated:animated]; } // Super [super viewWillDisappear:animated]; } - (void)willMoveToParentViewController:(UIViewController *)parent { if (parent && _hasBelongedToViewController) { [NSException raise:@"MWPhotoBrowser Instance Reuse" format:@"MWPhotoBrowser instances cannot be reused."]; } } - (void)didMoveToParentViewController:(UIViewController *)parent { if (!parent) _hasBelongedToViewController = YES; } #pragma mark - Nav Bar Appearance - (void)setNavBarAppearance:(BOOL)animated { [self.navigationController setNavigationBarHidden:NO animated:animated]; UINavigationBar *navBar = self.navigationController.navigationBar; navBar.tintColor = [UIColor whiteColor]; navBar.barTintColor = nil; navBar.shadowImage = nil; navBar.translucent = YES; navBar.barStyle = UIBarStyleBlackTranslucent; [navBar setBackgroundImage:nil forBarMetrics:UIBarMetricsDefault]; [navBar setBackgroundImage:nil forBarMetrics:UIBarMetricsLandscapePhone]; } - (void)storePreviousNavBarAppearance { _didSavePreviousStateOfNavBar = YES; _previousNavBarBarTintColor = self.navigationController.navigationBar.barTintColor; _previousNavBarTranslucent = self.navigationController.navigationBar.translucent; _previousNavBarTintColor = self.navigationController.navigationBar.tintColor; _previousNavBarHidden = self.navigationController.navigationBarHidden; _previousNavBarStyle = self.navigationController.navigationBar.barStyle; _previousNavigationBarBackgroundImageDefault = [self.navigationController.navigationBar backgroundImageForBarMetrics:UIBarMetricsDefault]; _previousNavigationBarBackgroundImageLandscapePhone = [self.navigationController.navigationBar backgroundImageForBarMetrics:UIBarMetricsLandscapePhone]; } - (void)restorePreviousNavBarAppearance:(BOOL)animated { if (_didSavePreviousStateOfNavBar) { [self.navigationController setNavigationBarHidden:_previousNavBarHidden animated:animated]; UINavigationBar *navBar = self.navigationController.navigationBar; navBar.tintColor = _previousNavBarTintColor; navBar.translucent = _previousNavBarTranslucent; navBar.barTintColor = _previousNavBarBarTintColor; navBar.barStyle = _previousNavBarStyle; [navBar setBackgroundImage:_previousNavigationBarBackgroundImageDefault forBarMetrics:UIBarMetricsDefault]; [navBar setBackgroundImage:_previousNavigationBarBackgroundImageLandscapePhone forBarMetrics:UIBarMetricsLandscapePhone]; // Restore back button if we need to if (_previousViewControllerBackButton) { UIViewController *previousViewController = [self.navigationController topViewController]; // We've disappeared so previous is now top previousViewController.navigationItem.backBarButtonItem = _previousViewControllerBackButton; _previousViewControllerBackButton = nil; } } } #pragma mark - Layout - (void)viewWillLayoutSubviews { [super viewWillLayoutSubviews]; [self layoutVisiblePages]; } - (void)layoutVisiblePages { // Flag _performingLayout = YES; // Toolbar _toolbar.frame = [self frameForToolbarAtOrientation:self.interfaceOrientation]; // Remember index NSUInteger indexPriorToLayout = _currentPageIndex; // Get paging scroll view frame to determine if anything needs changing CGRect pagingScrollViewFrame = [self frameForPagingScrollView]; // Frame needs changing if (!_skipNextPagingScrollViewPositioning) { _pagingScrollView.frame = pagingScrollViewFrame; } _skipNextPagingScrollViewPositioning = NO; // Recalculate contentSize based on current orientation _pagingScrollView.contentSize = [self contentSizeForPagingScrollView]; // Adjust frames and configuration of each visible page for (MWZoomingScrollView *page in _visiblePages) { NSUInteger index = page.index; page.frame = [self frameForPageAtIndex:index]; if (page.captionView) { page.captionView.frame = [self frameForCaptionView:page.captionView atIndex:index]; } if (page.selectedButton) { page.selectedButton.frame = [self frameForSelectedButton:page.selectedButton atIndex:index]; } if (page.playButton) { page.playButton.frame = [self frameForPlayButton:page.playButton atIndex:index]; } // Adjust scales if bounds has changed since last time if (!CGRectEqualToRect(_previousLayoutBounds, self.view.bounds)) { // Update zooms for new bounds [page setMaxMinZoomScalesForCurrentBounds]; _previousLayoutBounds = self.view.bounds; } } // Adjust video loading indicator if it's visible [self positionVideoLoadingIndicator]; // Adjust contentOffset to preserve page location based on values collected prior to location _pagingScrollView.contentOffset = [self contentOffsetForPageAtIndex:indexPriorToLayout]; [self didStartViewingPageAtIndex:_currentPageIndex]; // initial // Reset _currentPageIndex = indexPriorToLayout; _performingLayout = NO; } #pragma mark - Rotation - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation { return YES; } - (UIInterfaceOrientationMask)supportedInterfaceOrientations { return UIInterfaceOrientationMaskAll; } - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { // Remember page index before rotation _pageIndexBeforeRotation = _currentPageIndex; _rotating = YES; // In iOS 7 the nav bar gets shown after rotation, but might as well do this for everything! if ([self areControlsHidden]) { // Force hidden self.navigationController.navigationBarHidden = YES; } } - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { // Perform layout _currentPageIndex = _pageIndexBeforeRotation; // Delay control holding [self hideControlsAfterDelay]; // Layout [self layoutVisiblePages]; } - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation { _rotating = NO; // Ensure nav bar isn't re-displayed if ([self areControlsHidden]) { self.navigationController.navigationBarHidden = NO; self.navigationController.navigationBar.alpha = 0; } } #pragma mark - Data - (NSUInteger)currentIndex { return _currentPageIndex; } - (void)reloadData { // Reset _photoCount = NSNotFound; // Get data NSUInteger numberOfPhotos = [self numberOfPhotos]; [self releaseAllUnderlyingPhotos:YES]; [_photos removeAllObjects]; [_thumbPhotos removeAllObjects]; for (int i = 0; i < numberOfPhotos; i++) { [_photos addObject:[NSNull null]]; [_thumbPhotos addObject:[NSNull null]]; } // Update current page index if (numberOfPhotos > 0) { _currentPageIndex = MAX(0, MIN(_currentPageIndex, numberOfPhotos - 1)); } else { _currentPageIndex = 0; } // Update layout if ([self isViewLoaded]) { while (_pagingScrollView.subviews.count) { [[_pagingScrollView.subviews lastObject] removeFromSuperview]; } [self performLayout]; [self.view setNeedsLayout]; } } - (NSUInteger)numberOfPhotos { if (_photoCount == NSNotFound) { if ([_delegate respondsToSelector:@selector(numberOfPhotosInPhotoBrowser:)]) { _photoCount = [_delegate numberOfPhotosInPhotoBrowser:self]; } else if (_fixedPhotosArray) { _photoCount = _fixedPhotosArray.count; } } if (_photoCount == NSNotFound) _photoCount = 0; return _photoCount; } - (id)photoAtIndex:(NSUInteger)index { id photo = nil; if (index < _photos.count) { if ([_photos objectAtIndex:index] == [NSNull null]) { if ([_delegate respondsToSelector:@selector(photoBrowser:photoAtIndex:)]) { photo = [_delegate photoBrowser:self photoAtIndex:index]; } else if (_fixedPhotosArray && index < _fixedPhotosArray.count) { photo = [_fixedPhotosArray objectAtIndex:index]; } if (photo) [_photos replaceObjectAtIndex:index withObject:photo]; } else { photo = [_photos objectAtIndex:index]; } } return photo; } - (id)thumbPhotoAtIndex:(NSUInteger)index { id photo = nil; if (index < _thumbPhotos.count) { if ([_thumbPhotos objectAtIndex:index] == [NSNull null]) { if ([_delegate respondsToSelector:@selector(photoBrowser:thumbPhotoAtIndex:)]) { photo = [_delegate photoBrowser:self thumbPhotoAtIndex:index]; } if (photo) [_thumbPhotos replaceObjectAtIndex:index withObject:photo]; } else { photo = [_thumbPhotos objectAtIndex:index]; } } return photo; } - (MWCaptionView *)captionViewForPhotoAtIndex:(NSUInteger)index { MWCaptionView *captionView = nil; if ([_delegate respondsToSelector:@selector(photoBrowser:captionViewForPhotoAtIndex:)]) { captionView = [_delegate photoBrowser:self captionViewForPhotoAtIndex:index]; } else { id photo = [self photoAtIndex:index]; if ([photo respondsToSelector:@selector(caption)]) { if ([photo caption]) captionView = [[MWCaptionView alloc] initWithPhoto:photo]; } } captionView.alpha = [self areControlsHidden] ? 0 : 1; // Initial alpha return captionView; } - (BOOL)photoIsSelectedAtIndex:(NSUInteger)index { BOOL value = NO; if (_displaySelectionButtons) { if ([self.delegate respondsToSelector:@selector(photoBrowser:isPhotoSelectedAtIndex:)]) { value = [self.delegate photoBrowser:self isPhotoSelectedAtIndex:index]; } } return value; } - (void)setPhotoSelected:(BOOL)selected atIndex:(NSUInteger)index { if (_displaySelectionButtons) { if ([self.delegate respondsToSelector:@selector(photoBrowser:photoAtIndex:selectedChanged:)]) { [self.delegate photoBrowser:self photoAtIndex:index selectedChanged:selected]; } } } - (UIImage *)imageForPhoto:(id)photo { if (photo) { // Get image or obtain in background if ([photo underlyingImage]) { return [photo underlyingImage]; } else { [photo loadUnderlyingImageAndNotify]; } } return nil; } - (void)loadAdjacentPhotosIfNecessary:(id)photo { MWZoomingScrollView *page = [self pageDisplayingPhoto:photo]; if (page) { // If page is current page then initiate loading of previous and next pages NSUInteger pageIndex = page.index; if (_currentPageIndex == pageIndex) { if (pageIndex > 0) { // Preload index - 1 id photo = [self photoAtIndex:pageIndex-1]; if (![photo underlyingImage]) { [photo loadUnderlyingImageAndNotify]; MWLog(@"Pre-loading image at index %lu", (unsigned long)pageIndex-1); } } if (pageIndex < [self numberOfPhotos] - 1) { // Preload index + 1 id photo = [self photoAtIndex:pageIndex+1]; if (![photo underlyingImage]) { [photo loadUnderlyingImageAndNotify]; MWLog(@"Pre-loading image at index %lu", (unsigned long)pageIndex+1); } } } } } #pragma mark - MWPhoto Loading Notification - (void)handleMWPhotoLoadingDidEndNotification:(NSNotification *)notification { id photo = [notification object]; MWZoomingScrollView *page = [self pageDisplayingPhoto:photo]; if (page) { if ([photo underlyingImage]) { // Successful load [page displayImage]; [self loadAdjacentPhotosIfNecessary:photo]; } else { // Failed to load [page displayImageFailure]; } // Update nav [self updateNavigation]; } } #pragma mark - Paging - (void)tilePages { // Calculate which pages should be visible // Ignore padding as paging bounces encroach on that // and lead to false page loads CGRect visibleBounds = _pagingScrollView.bounds; NSInteger iFirstIndex = (NSInteger)floorf((CGRectGetMinX(visibleBounds)+PADDING*2) / CGRectGetWidth(visibleBounds)); NSInteger iLastIndex = (NSInteger)floorf((CGRectGetMaxX(visibleBounds)-PADDING*2-1) / CGRectGetWidth(visibleBounds)); if (iFirstIndex < 0) iFirstIndex = 0; if (iFirstIndex > [self numberOfPhotos] - 1) iFirstIndex = [self numberOfPhotos] - 1; if (iLastIndex < 0) iLastIndex = 0; if (iLastIndex > [self numberOfPhotos] - 1) iLastIndex = [self numberOfPhotos] - 1; // Recycle no longer needed pages NSInteger pageIndex; for (MWZoomingScrollView *page in _visiblePages) { pageIndex = page.index; if (pageIndex < (NSUInteger)iFirstIndex || pageIndex > (NSUInteger)iLastIndex) { [_recycledPages addObject:page]; [page.captionView removeFromSuperview]; [page.selectedButton removeFromSuperview]; [page.playButton removeFromSuperview]; [page prepareForReuse]; [page removeFromSuperview]; MWLog(@"Removed page at index %lu", (unsigned long)pageIndex); } } [_visiblePages minusSet:_recycledPages]; while (_recycledPages.count > 2) // Only keep 2 recycled pages [_recycledPages removeObject:[_recycledPages anyObject]]; // Add missing pages for (NSUInteger index = (NSUInteger)iFirstIndex; index <= (NSUInteger)iLastIndex; index++) { if (![self isDisplayingPageForIndex:index]) { // Add new page MWZoomingScrollView *page = [self dequeueRecycledPage]; if (!page) { page = [[MWZoomingScrollView alloc] initWithPhotoBrowser:self]; } [_visiblePages addObject:page]; [self configurePage:page forIndex:index]; [_pagingScrollView addSubview:page]; MWLog(@"Added page at index %lu", (unsigned long)index); // Add caption MWCaptionView *captionView = [self captionViewForPhotoAtIndex:index]; if (captionView) { captionView.frame = [self frameForCaptionView:captionView atIndex:index]; [_pagingScrollView addSubview:captionView]; page.captionView = captionView; } // Add play button if needed if (page.displayingVideo) { UIButton *playButton = [UIButton buttonWithType:UIButtonTypeCustom]; [playButton setImage:[UIImage imageForResourcePath:@"MWPhotoBrowser.bundle/PlayButtonOverlayLarge" ofType:@"png" inBundle:[NSBundle bundleForClass:[self class]]] forState:UIControlStateNormal]; [playButton setImage:[UIImage imageForResourcePath:@"MWPhotoBrowser.bundle/PlayButtonOverlayLargeTap" ofType:@"png" inBundle:[NSBundle bundleForClass:[self class]]] forState:UIControlStateHighlighted]; [playButton addTarget:self action:@selector(playButtonTapped:) forControlEvents:UIControlEventTouchUpInside]; [playButton sizeToFit]; playButton.frame = [self frameForPlayButton:playButton atIndex:index]; [_pagingScrollView addSubview:playButton]; page.playButton = playButton; } // Add selected button if (self.displaySelectionButtons) { UIButton *selectedButton = [UIButton buttonWithType:UIButtonTypeCustom]; [selectedButton setImage:[UIImage imageForResourcePath:@"MWPhotoBrowser.bundle/ImageSelectedOff" ofType:@"png" inBundle:[NSBundle bundleForClass:[self class]]] forState:UIControlStateNormal]; UIImage *selectedOnImage; if (self.customImageSelectedIconName) { selectedOnImage = [UIImage imageNamed:self.customImageSelectedIconName]; } else { selectedOnImage = [UIImage imageForResourcePath:@"MWPhotoBrowser.bundle/ImageSelectedOn" ofType:@"png" inBundle:[NSBundle bundleForClass:[self class]]]; } [selectedButton setImage:selectedOnImage forState:UIControlStateSelected]; [selectedButton sizeToFit]; selectedButton.adjustsImageWhenHighlighted = NO; [selectedButton addTarget:self action:@selector(selectedButtonTapped:) forControlEvents:UIControlEventTouchUpInside]; selectedButton.frame = [self frameForSelectedButton:selectedButton atIndex:index]; [_pagingScrollView addSubview:selectedButton]; page.selectedButton = selectedButton; selectedButton.selected = [self photoIsSelectedAtIndex:index]; } } } } - (void)updateVisiblePageStates { NSSet *copy = [_visiblePages copy]; for (MWZoomingScrollView *page in copy) { // Update selection page.selectedButton.selected = [self photoIsSelectedAtIndex:page.index]; } } - (BOOL)isDisplayingPageForIndex:(NSUInteger)index { for (MWZoomingScrollView *page in _visiblePages) if (page.index == index) return YES; return NO; } - (MWZoomingScrollView *)pageDisplayedAtIndex:(NSUInteger)index { MWZoomingScrollView *thePage = nil; for (MWZoomingScrollView *page in _visiblePages) { if (page.index == index) { thePage = page; break; } } return thePage; } - (MWZoomingScrollView *)pageDisplayingPhoto:(id)photo { MWZoomingScrollView *thePage = nil; for (MWZoomingScrollView *page in _visiblePages) { if (page.photo == photo) { thePage = page; break; } } return thePage; } - (void)configurePage:(MWZoomingScrollView *)page forIndex:(NSUInteger)index { page.frame = [self frameForPageAtIndex:index]; page.index = index; page.photo = [self photoAtIndex:index]; } - (MWZoomingScrollView *)dequeueRecycledPage { MWZoomingScrollView *page = [_recycledPages anyObject]; if (page) { [_recycledPages removeObject:page]; } return page; } // Handle page changes - (void)didStartViewingPageAtIndex:(NSUInteger)index { // Handle 0 photos if (![self numberOfPhotos]) { // Show controls [self setControlsHidden:NO animated:YES permanent:YES]; return; } // Handle video on page change if (!_rotating && index != _currentVideoIndex) { [self clearCurrentVideo]; } // Release images further away than +/-1 NSUInteger i; if (index > 0) { // Release anything < index - 1 for (i = 0; i < index-1; i++) { id photo = [_photos objectAtIndex:i]; if (photo != [NSNull null]) { [photo unloadUnderlyingImage]; [_photos replaceObjectAtIndex:i withObject:[NSNull null]]; MWLog(@"Released underlying image at index %lu", (unsigned long)i); } } } if (index < [self numberOfPhotos] - 1) { // Release anything > index + 1 for (i = index + 2; i < _photos.count; i++) { id photo = [_photos objectAtIndex:i]; if (photo != [NSNull null]) { [photo unloadUnderlyingImage]; [_photos replaceObjectAtIndex:i withObject:[NSNull null]]; MWLog(@"Released underlying image at index %lu", (unsigned long)i); } } } // Load adjacent images if needed and the photo is already // loaded. Also called after photo has been loaded in background id currentPhoto = [self photoAtIndex:index]; if ([currentPhoto underlyingImage]) { // photo loaded so load ajacent now [self loadAdjacentPhotosIfNecessary:currentPhoto]; } // Notify delegate if (index != _previousPageIndex) { if ([_delegate respondsToSelector:@selector(photoBrowser:didDisplayPhotoAtIndex:)]) [_delegate photoBrowser:self didDisplayPhotoAtIndex:index]; _previousPageIndex = index; } // Update nav [self updateNavigation]; } #pragma mark - Frame Calculations - (CGRect)frameForPagingScrollView { CGRect frame = self.view.bounds;// [[UIScreen mainScreen] bounds]; frame.origin.x -= PADDING; frame.size.width += (2 * PADDING); return CGRectIntegral(frame); } - (CGRect)frameForPageAtIndex:(NSUInteger)index { // We have to use our paging scroll view's bounds, not frame, to calculate the page placement. When the device is in // landscape orientation, the frame will still be in portrait because the pagingScrollView is the root view controller's // view, so its frame is in window coordinate space, which is never rotated. Its bounds, however, will be in landscape // because it has a rotation transform applied. CGRect bounds = _pagingScrollView.bounds; CGRect pageFrame = bounds; pageFrame.size.width -= (2 * PADDING); pageFrame.origin.x = (bounds.size.width * index) + PADDING; return CGRectIntegral(pageFrame); } - (CGSize)contentSizeForPagingScrollView { // We have to use the paging scroll view's bounds to calculate the contentSize, for the same reason outlined above. CGRect bounds = _pagingScrollView.bounds; return CGSizeMake(bounds.size.width * [self numberOfPhotos], bounds.size.height); } - (CGPoint)contentOffsetForPageAtIndex:(NSUInteger)index { CGFloat pageWidth = _pagingScrollView.bounds.size.width; CGFloat newOffset = index * pageWidth; return CGPointMake(newOffset, 0); } - (CGRect)frameForToolbarAtOrientation:(UIInterfaceOrientation)orientation { CGFloat height = 44; if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone && UIInterfaceOrientationIsLandscape(orientation)) height = 32; return CGRectIntegral(CGRectMake(0, self.view.bounds.size.height - height, self.view.bounds.size.width, height)); } - (CGRect)frameForCaptionView:(MWCaptionView *)captionView atIndex:(NSUInteger)index { CGRect pageFrame = [self frameForPageAtIndex:index]; CGSize captionSize = [captionView sizeThatFits:CGSizeMake(pageFrame.size.width, 0)]; CGRect captionFrame = CGRectMake(pageFrame.origin.x, pageFrame.size.height - captionSize.height - (_toolbar.superview?_toolbar.frame.size.height:0), pageFrame.size.width, captionSize.height); return CGRectIntegral(captionFrame); } - (CGRect)frameForSelectedButton:(UIButton *)selectedButton atIndex:(NSUInteger)index { CGRect pageFrame = [self frameForPageAtIndex:index]; CGFloat padding = 20; CGFloat yOffset = 0; if (![self areControlsHidden]) { UINavigationBar *navBar = self.navigationController.navigationBar; yOffset = navBar.frame.origin.y + navBar.frame.size.height; } CGRect selectedButtonFrame = CGRectMake(pageFrame.origin.x + pageFrame.size.width - selectedButton.frame.size.width - padding, padding + yOffset, selectedButton.frame.size.width, selectedButton.frame.size.height); return CGRectIntegral(selectedButtonFrame); } - (CGRect)frameForPlayButton:(UIButton *)playButton atIndex:(NSUInteger)index { CGRect pageFrame = [self frameForPageAtIndex:index]; return CGRectMake(floorf(CGRectGetMidX(pageFrame) - playButton.frame.size.width / 2), floorf(CGRectGetMidY(pageFrame) - playButton.frame.size.height / 2), playButton.frame.size.width, playButton.frame.size.height); } #pragma mark - UIScrollView Delegate - (void)scrollViewDidScroll:(UIScrollView *)scrollView { // Checks if (!_viewIsActive || _performingLayout || _rotating) return; // Tile pages [self tilePages]; // Calculate current page CGRect visibleBounds = _pagingScrollView.bounds; NSInteger index = (NSInteger)(floorf(CGRectGetMidX(visibleBounds) / CGRectGetWidth(visibleBounds))); if (index < 0) index = 0; if (index > [self numberOfPhotos] - 1) index = [self numberOfPhotos] - 1; NSUInteger previousCurrentPage = _currentPageIndex; _currentPageIndex = index; if (_currentPageIndex != previousCurrentPage) { [self didStartViewingPageAtIndex:index]; } } - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { // Hide controls when dragging begins [self setControlsHidden:YES animated:YES permanent:NO]; } - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { // Update nav when page changes [self updateNavigation]; } #pragma mark - Navigation - (void)updateNavigation { // Title NSUInteger numberOfPhotos = [self numberOfPhotos]; if (_gridController) { if (_gridController.selectionMode) { self.title = NSLocalizedString(@"Select Photos", nil); } else { NSString *photosText; if (numberOfPhotos == 1) { photosText = NSLocalizedString(@"photo", @"Used in the context: '1 photo'"); } else { photosText = NSLocalizedString(@"photos", @"Used in the context: '3 photos'"); } self.title = [NSString stringWithFormat:@"%lu %@", (unsigned long)numberOfPhotos, photosText]; } } else if (numberOfPhotos > 1) { if ([_delegate respondsToSelector:@selector(photoBrowser:titleForPhotoAtIndex:)]) { self.title = [_delegate photoBrowser:self titleForPhotoAtIndex:_currentPageIndex]; } else { self.title = [NSString stringWithFormat:@"%lu %@ %lu", (unsigned long)(_currentPageIndex+1), NSLocalizedString(@"of", @"Used in the context: 'Showing 1 of 3 items'"), (unsigned long)numberOfPhotos]; } } else { self.title = nil; } // Buttons _previousButton.enabled = (_currentPageIndex > 0); _nextButton.enabled = (_currentPageIndex < numberOfPhotos - 1); // Disable action button if there is no image or it's a video MWPhoto *photo = [self photoAtIndex:_currentPageIndex]; if ([photo underlyingImage] == nil || ([photo respondsToSelector:@selector(isVideo)] && photo.isVideo)) { _actionButton.enabled = NO; _actionButton.tintColor = [UIColor clearColor]; // Tint to hide button } else { _actionButton.enabled = YES; _actionButton.tintColor = nil; } } - (void)jumpToPageAtIndex:(NSUInteger)index animated:(BOOL)animated { // Change page if (index < [self numberOfPhotos]) { CGRect pageFrame = [self frameForPageAtIndex:index]; [_pagingScrollView setContentOffset:CGPointMake(pageFrame.origin.x - PADDING, 0) animated:animated]; [self updateNavigation]; } // Update timer to give more time [self hideControlsAfterDelay]; } - (void)gotoPreviousPage { [self showPreviousPhotoAnimated:NO]; } - (void)gotoNextPage { [self showNextPhotoAnimated:NO]; } - (void)showPreviousPhotoAnimated:(BOOL)animated { [self jumpToPageAtIndex:_currentPageIndex-1 animated:animated]; } - (void)showNextPhotoAnimated:(BOOL)animated { [self jumpToPageAtIndex:_currentPageIndex+1 animated:animated]; } #pragma mark - Interactions - (void)selectedButtonTapped:(id)sender { UIButton *selectedButton = (UIButton *)sender; selectedButton.selected = !selectedButton.selected; NSUInteger index = NSUIntegerMax; for (MWZoomingScrollView *page in _visiblePages) { if (page.selectedButton == selectedButton) { index = page.index; break; } } if (index != NSUIntegerMax) { [self setPhotoSelected:selectedButton.selected atIndex:index]; } } - (void)playButtonTapped:(id)sender { // Ignore if we're already playing a video if (_currentVideoIndex != NSUIntegerMax) { return; } NSUInteger index = [self indexForPlayButton:sender]; if (index != NSUIntegerMax) { if (!_currentVideoPlayerViewController) { [self playVideoAtIndex:index]; } } } - (NSUInteger)indexForPlayButton:(UIView *)playButton { NSUInteger index = NSUIntegerMax; for (MWZoomingScrollView *page in _visiblePages) { if (page.playButton == playButton) { index = page.index; break; } } return index; } #pragma mark - Video - (void)playVideoAtIndex:(NSUInteger)index { id photo = [self photoAtIndex:index]; if ([photo respondsToSelector:@selector(getVideoURL:)]) { // Valid for playing [self clearCurrentVideo]; _currentVideoIndex = index; [self setVideoLoadingIndicatorVisible:YES atPageIndex:index]; // Get video and play typeof(self) __weak weakSelf = self; [photo getVideoURL:^(NSURL *url) { dispatch_async(dispatch_get_main_queue(), ^{ // If the video is not playing anymore then bail typeof(self) strongSelf = weakSelf; if (!strongSelf) return; if (strongSelf->_currentVideoIndex != index || !strongSelf->_viewIsActive) { return; } if (url) { [weakSelf _playVideo:url atPhotoIndex:index]; } else { [weakSelf setVideoLoadingIndicatorVisible:NO atPageIndex:index]; } }); }]; } } - (void)_playVideo:(NSURL *)videoURL atPhotoIndex:(NSUInteger)index { // Setup player _currentVideoPlayerViewController = [[MPMoviePlayerViewController alloc] initWithContentURL:videoURL]; [_currentVideoPlayerViewController.moviePlayer prepareToPlay]; _currentVideoPlayerViewController.moviePlayer.shouldAutoplay = YES; _currentVideoPlayerViewController.moviePlayer.scalingMode = MPMovieScalingModeAspectFit; _currentVideoPlayerViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; // Remove the movie player view controller from the "playback did finish" notification observers // Observe ourselves so we can get it to use the crossfade transition [[NSNotificationCenter defaultCenter] removeObserver:_currentVideoPlayerViewController name:MPMoviePlayerPlaybackDidFinishNotification object:_currentVideoPlayerViewController.moviePlayer]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(videoFinishedCallback:) name:MPMoviePlayerPlaybackDidFinishNotification object:_currentVideoPlayerViewController.moviePlayer]; // Show [self presentViewController:_currentVideoPlayerViewController animated:YES completion:nil]; } - (void)videoFinishedCallback:(NSNotification*)notification { // Remove observer [[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerPlaybackDidFinishNotification object:_currentVideoPlayerViewController.moviePlayer]; // Clear up [self clearCurrentVideo]; // Dismiss BOOL error = [[[notification userInfo] objectForKey:MPMoviePlayerPlaybackDidFinishReasonUserInfoKey] intValue] == MPMovieFinishReasonPlaybackError; if (error) { // Error occured so dismiss with a delay incase error was immediate and we need to wait to dismiss the VC dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self dismissViewControllerAnimated:YES completion:nil]; }); } else { [self dismissViewControllerAnimated:YES completion:nil]; } } - (void)clearCurrentVideo { [_currentVideoPlayerViewController.moviePlayer stop]; [_currentVideoLoadingIndicator removeFromSuperview]; _currentVideoPlayerViewController = nil; _currentVideoLoadingIndicator = nil; [[self pageDisplayedAtIndex:_currentVideoIndex] playButton].hidden = NO; _currentVideoIndex = NSUIntegerMax; } - (void)setVideoLoadingIndicatorVisible:(BOOL)visible atPageIndex:(NSUInteger)pageIndex { if (_currentVideoLoadingIndicator && !visible) { [_currentVideoLoadingIndicator removeFromSuperview]; _currentVideoLoadingIndicator = nil; [[self pageDisplayedAtIndex:pageIndex] playButton].hidden = NO; } else if (!_currentVideoLoadingIndicator && visible) { _currentVideoLoadingIndicator = [[UIActivityIndicatorView alloc] initWithFrame:CGRectZero]; [_currentVideoLoadingIndicator sizeToFit]; [_currentVideoLoadingIndicator startAnimating]; [_pagingScrollView addSubview:_currentVideoLoadingIndicator]; [self positionVideoLoadingIndicator]; [[self pageDisplayedAtIndex:pageIndex] playButton].hidden = YES; } } - (void)positionVideoLoadingIndicator { if (_currentVideoLoadingIndicator && _currentVideoIndex != NSUIntegerMax) { CGRect frame = [self frameForPageAtIndex:_currentVideoIndex]; _currentVideoLoadingIndicator.center = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame)); } } #pragma mark - Grid - (void)showGridAnimated { [self showGrid:YES]; } - (void)showGrid:(BOOL)animated { if (_gridController) return; // Clear video [self clearCurrentVideo]; // Init grid controller _gridController = [[MWGridViewController alloc] init]; _gridController.initialContentOffset = _currentGridContentOffset; _gridController.browser = self; _gridController.selectionMode = _displaySelectionButtons; _gridController.view.frame = self.view.bounds; _gridController.view.frame = CGRectOffset(_gridController.view.frame, 0, (self.startOnGrid ? -1 : 1) * self.view.bounds.size.height); // Stop specific layout being triggered _skipNextPagingScrollViewPositioning = YES; // Add as a child view controller [self addChildViewController:_gridController]; [self.view addSubview:_gridController.view]; // Perform any adjustments [_gridController.view layoutIfNeeded]; [_gridController adjustOffsetsAsRequired]; // Hide action button on nav bar if it exists if (self.navigationItem.rightBarButtonItem == _actionButton) { _gridPreviousRightNavItem = _actionButton; [self.navigationItem setRightBarButtonItem:nil animated:YES]; } else { _gridPreviousRightNavItem = nil; } // Update [self updateNavigation]; [self setControlsHidden:NO animated:YES permanent:YES]; // Animate grid in and photo scroller out [_gridController willMoveToParentViewController:self]; [UIView animateWithDuration:animated ? 0.3 : 0 animations:^(void) { _gridController.view.frame = self.view.bounds; CGRect newPagingFrame = [self frameForPagingScrollView]; newPagingFrame = CGRectOffset(newPagingFrame, 0, (self.startOnGrid ? 1 : -1) * newPagingFrame.size.height); _pagingScrollView.frame = newPagingFrame; } completion:^(BOOL finished) { [_gridController didMoveToParentViewController:self]; }]; } - (void)hideGrid { if (!_gridController) return; // Remember previous content offset _currentGridContentOffset = _gridController.collectionView.contentOffset; // Restore action button if it was removed if (_gridPreviousRightNavItem == _actionButton && _actionButton) { [self.navigationItem setRightBarButtonItem:_gridPreviousRightNavItem animated:YES]; } // Position prior to hide animation CGRect newPagingFrame = [self frameForPagingScrollView]; newPagingFrame = CGRectOffset(newPagingFrame, 0, (self.startOnGrid ? 1 : -1) * newPagingFrame.size.height); _pagingScrollView.frame = newPagingFrame; // Remember and remove controller now so things can detect a nil grid controller MWGridViewController *tmpGridController = _gridController; _gridController = nil; // Update [self updateNavigation]; [self updateVisiblePageStates]; // Animate, hide grid and show paging scroll view [UIView animateWithDuration:0.3 animations:^{ tmpGridController.view.frame = CGRectOffset(self.view.bounds, 0, (self.startOnGrid ? -1 : 1) * self.view.bounds.size.height); _pagingScrollView.frame = [self frameForPagingScrollView]; } completion:^(BOOL finished) { [tmpGridController willMoveToParentViewController:nil]; [tmpGridController.view removeFromSuperview]; [tmpGridController removeFromParentViewController]; [self setControlsHidden:NO animated:YES permanent:NO]; // retrigger timer }]; } #pragma mark - Control Hiding / Showing // If permanent then we don't set timers to hide again // Fades all controls on iOS 5 & 6, and iOS 7 controls slide and fade - (void)setControlsHidden:(BOOL)hidden animated:(BOOL)animated permanent:(BOOL)permanent { // Force visible if (![self numberOfPhotos] || _gridController || _alwaysShowControls) hidden = NO; // Cancel any timers [self cancelControlHiding]; // Animations & positions CGFloat animatonOffset = 20; CGFloat animationDuration = (animated ? 0.35 : 0); // Status bar if (!_leaveStatusBarAlone) { // Hide status bar if (!_isVCBasedStatusBarAppearance) { // Non-view controller based [[UIApplication sharedApplication] setStatusBarHidden:hidden withAnimation:animated ? UIStatusBarAnimationSlide : UIStatusBarAnimationNone]; } else { // View controller based so animate away _statusBarShouldBeHidden = hidden; [UIView animateWithDuration:animationDuration animations:^(void) { [self setNeedsStatusBarAppearanceUpdate]; } completion:^(BOOL finished) {}]; } } // Toolbar, nav bar and captions // Pre-appear animation positions for sliding if ([self areControlsHidden] && !hidden && animated) { // Toolbar _toolbar.frame = CGRectOffset([self frameForToolbarAtOrientation:self.interfaceOrientation], 0, animatonOffset); // Captions for (MWZoomingScrollView *page in _visiblePages) { if (page.captionView) { MWCaptionView *v = page.captionView; // Pass any index, all we're interested in is the Y CGRect captionFrame = [self frameForCaptionView:v atIndex:0]; captionFrame.origin.x = v.frame.origin.x; // Reset X v.frame = CGRectOffset(captionFrame, 0, animatonOffset); } } } [UIView animateWithDuration:animationDuration animations:^(void) { CGFloat alpha = hidden ? 0 : 1; // Nav bar slides up on it's own on iOS 7+ [self.navigationController.navigationBar setAlpha:alpha]; // Toolbar _toolbar.frame = [self frameForToolbarAtOrientation:self.interfaceOrientation]; if (hidden) _toolbar.frame = CGRectOffset(_toolbar.frame, 0, animatonOffset); _toolbar.alpha = alpha; // Captions for (MWZoomingScrollView *page in _visiblePages) { if (page.captionView) { MWCaptionView *v = page.captionView; // Pass any index, all we're interested in is the Y CGRect captionFrame = [self frameForCaptionView:v atIndex:0]; captionFrame.origin.x = v.frame.origin.x; // Reset X if (hidden) captionFrame = CGRectOffset(captionFrame, 0, animatonOffset); v.frame = captionFrame; v.alpha = alpha; } } // Selected buttons for (MWZoomingScrollView *page in _visiblePages) { if (page.selectedButton) { UIButton *v = page.selectedButton; CGRect newFrame = [self frameForSelectedButton:v atIndex:0]; newFrame.origin.x = v.frame.origin.x; v.frame = newFrame; } } } completion:^(BOOL finished) {}]; // Control hiding timer // Will cancel existing timer but only begin hiding if // they are visible if (!permanent) [self hideControlsAfterDelay]; } - (BOOL)prefersStatusBarHidden { if (!_leaveStatusBarAlone) { return _statusBarShouldBeHidden; } else { return [self presentingViewControllerPrefersStatusBarHidden]; } } - (UIStatusBarStyle)preferredStatusBarStyle { return UIStatusBarStyleLightContent; } - (UIStatusBarAnimation)preferredStatusBarUpdateAnimation { return UIStatusBarAnimationSlide; } - (void)cancelControlHiding { // If a timer exists then cancel and release if (_controlVisibilityTimer) { [_controlVisibilityTimer invalidate]; _controlVisibilityTimer = nil; } } // Enable/disable control visiblity timer - (void)hideControlsAfterDelay { if (![self areControlsHidden]) { [self cancelControlHiding]; _controlVisibilityTimer = [NSTimer scheduledTimerWithTimeInterval:self.delayToHideElements target:self selector:@selector(hideControls) userInfo:nil repeats:NO]; } } - (BOOL)areControlsHidden { return (_toolbar.alpha == 0); } - (void)hideControls { [self setControlsHidden:YES animated:YES permanent:NO]; } - (void)showControls { [self setControlsHidden:NO animated:YES permanent:NO]; } - (void)toggleControls { [self setControlsHidden:![self areControlsHidden] animated:YES permanent:NO]; } #pragma mark - Properties - (void)setCurrentPhotoIndex:(NSUInteger)index { // Validate NSUInteger photoCount = [self numberOfPhotos]; if (photoCount == 0) { index = 0; } else { if (index >= photoCount) index = [self numberOfPhotos]-1; } _currentPageIndex = index; if ([self isViewLoaded]) { [self jumpToPageAtIndex:index animated:NO]; if (!_viewIsActive) [self tilePages]; // Force tiling if view is not visible } } #pragma mark - Misc - (void)doneButtonPressed:(id)sender { // Only if we're modal and there's a done button if (_doneButton) { // See if we actually just want to show/hide grid if (self.enableGrid) { if (self.startOnGrid && !_gridController) { [self showGrid:YES]; return; } else if (!self.startOnGrid && _gridController) { [self hideGrid]; return; } } // Dismiss view controller if ([_delegate respondsToSelector:@selector(photoBrowserDidFinishModalPresentation:)]) { // Call delegate method and let them dismiss us [_delegate photoBrowserDidFinishModalPresentation:self]; } else { [self dismissViewControllerAnimated:YES completion:nil]; } } } #pragma mark - Actions - (void)actionButtonPressed:(id)sender { // Only react when image has loaded id photo = [self photoAtIndex:_currentPageIndex]; if ([self numberOfPhotos] > 0 && [photo underlyingImage]) { // If they have defined a delegate method then just message them if ([self.delegate respondsToSelector:@selector(photoBrowser:actionButtonPressedForPhotoAtIndex:)]) { // Let delegate handle things [self.delegate photoBrowser:self actionButtonPressedForPhotoAtIndex:_currentPageIndex]; } else { // Show activity view controller NSMutableArray *items = [NSMutableArray arrayWithObject:[photo underlyingImage]]; if (photo.caption) { [items addObject:photo.caption]; } self.activityViewController = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil]; // Show loading spinner after a couple of seconds double delayInSeconds = 2.0; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ if (self.activityViewController) { [self showProgressHUDWithMessage:nil]; } }); // Show typeof(self) __weak weakSelf = self; [self.activityViewController setCompletionHandler:^(NSString *activityType, BOOL completed) { weakSelf.activityViewController = nil; [weakSelf hideControlsAfterDelay]; [weakSelf hideProgressHUD:YES]; }]; // iOS 8 - Set the Anchor Point for the popover if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8")) { self.activityViewController.popoverPresentationController.barButtonItem = _actionButton; } [self presentViewController:self.activityViewController animated:YES completion:nil]; } // Keep controls hidden [self setControlsHidden:NO animated:YES permanent:YES]; } } #pragma mark - Action Progress - (MBProgressHUD *)progressHUD { if (!_progressHUD) { _progressHUD = [[MBProgressHUD alloc] initWithView:self.view]; _progressHUD.minSize = CGSizeMake(120, 120); _progressHUD.minShowTime = 1; [self.view addSubview:_progressHUD]; } return _progressHUD; } - (void)showProgressHUDWithMessage:(NSString *)message { self.progressHUD.labelText = message; self.progressHUD.mode = MBProgressHUDModeIndeterminate; [self.progressHUD show:YES]; self.navigationController.navigationBar.userInteractionEnabled = NO; } - (void)hideProgressHUD:(BOOL)animated { [self.progressHUD hide:animated]; self.navigationController.navigationBar.userInteractionEnabled = YES; } - (void)showProgressHUDCompleteMessage:(NSString *)message { if (message) { if (self.progressHUD.isHidden) [self.progressHUD show:YES]; self.progressHUD.labelText = message; self.progressHUD.mode = MBProgressHUDModeCustomView; [self.progressHUD hide:YES afterDelay:1.5]; } else { [self.progressHUD hide:YES]; } self.navigationController.navigationBar.userInteractionEnabled = YES; } @end ================================================ FILE: Pod/Classes/MWPhotoBrowserPrivate.h ================================================ // // MWPhotoBrowser_Private.h // MWPhotoBrowser // // Created by Michael Waterfall on 08/10/2013. // // #import #import #import #import "MWGridViewController.h" #import "MWZoomingScrollView.h" // Declare private methods of browser @interface MWPhotoBrowser () { // Data NSUInteger _photoCount; NSMutableArray *_photos; NSMutableArray *_thumbPhotos; NSArray *_fixedPhotosArray; // Provided via init // Views UIScrollView *_pagingScrollView; // Paging & layout NSMutableSet *_visiblePages, *_recycledPages; NSUInteger _currentPageIndex; NSUInteger _previousPageIndex; CGRect _previousLayoutBounds; NSUInteger _pageIndexBeforeRotation; // Navigation & controls UIToolbar *_toolbar; NSTimer *_controlVisibilityTimer; UIBarButtonItem *_previousButton, *_nextButton, *_actionButton, *_doneButton; MBProgressHUD *_progressHUD; // Grid MWGridViewController *_gridController; UIBarButtonItem *_gridPreviousLeftNavItem; UIBarButtonItem *_gridPreviousRightNavItem; // Appearance BOOL _previousNavBarHidden; BOOL _previousNavBarTranslucent; UIBarStyle _previousNavBarStyle; UIStatusBarStyle _previousStatusBarStyle; UIColor *_previousNavBarTintColor; UIColor *_previousNavBarBarTintColor; UIBarButtonItem *_previousViewControllerBackButton; UIImage *_previousNavigationBarBackgroundImageDefault; UIImage *_previousNavigationBarBackgroundImageLandscapePhone; // Video MPMoviePlayerViewController *_currentVideoPlayerViewController; NSUInteger _currentVideoIndex; UIActivityIndicatorView *_currentVideoLoadingIndicator; // Misc BOOL _hasBelongedToViewController; BOOL _isVCBasedStatusBarAppearance; BOOL _statusBarShouldBeHidden; BOOL _displayActionButton; BOOL _leaveStatusBarAlone; BOOL _performingLayout; BOOL _rotating; BOOL _viewIsActive; // active as in it's in the view heirarchy BOOL _didSavePreviousStateOfNavBar; BOOL _skipNextPagingScrollViewPositioning; BOOL _viewHasAppearedInitially; CGPoint _currentGridContentOffset; } // Properties @property (nonatomic) UIActivityViewController *activityViewController; // Layout - (void)layoutVisiblePages; - (void)performLayout; - (BOOL)presentingViewControllerPrefersStatusBarHidden; // Nav Bar Appearance - (void)setNavBarAppearance:(BOOL)animated; - (void)storePreviousNavBarAppearance; - (void)restorePreviousNavBarAppearance:(BOOL)animated; // Paging - (void)tilePages; - (BOOL)isDisplayingPageForIndex:(NSUInteger)index; - (MWZoomingScrollView *)pageDisplayedAtIndex:(NSUInteger)index; - (MWZoomingScrollView *)pageDisplayingPhoto:(id)photo; - (MWZoomingScrollView *)dequeueRecycledPage; - (void)configurePage:(MWZoomingScrollView *)page forIndex:(NSUInteger)index; - (void)didStartViewingPageAtIndex:(NSUInteger)index; // Frames - (CGRect)frameForPagingScrollView; - (CGRect)frameForPageAtIndex:(NSUInteger)index; - (CGSize)contentSizeForPagingScrollView; - (CGPoint)contentOffsetForPageAtIndex:(NSUInteger)index; - (CGRect)frameForToolbarAtOrientation:(UIInterfaceOrientation)orientation; - (CGRect)frameForCaptionView:(MWCaptionView *)captionView atIndex:(NSUInteger)index; - (CGRect)frameForSelectedButton:(UIButton *)selectedButton atIndex:(NSUInteger)index; // Navigation - (void)updateNavigation; - (void)jumpToPageAtIndex:(NSUInteger)index animated:(BOOL)animated; - (void)gotoPreviousPage; - (void)gotoNextPage; // Grid - (void)showGrid:(BOOL)animated; - (void)hideGrid; // Controls - (void)cancelControlHiding; - (void)hideControlsAfterDelay; - (void)setControlsHidden:(BOOL)hidden animated:(BOOL)animated permanent:(BOOL)permanent; - (void)toggleControls; - (BOOL)areControlsHidden; // Data - (NSUInteger)numberOfPhotos; - (id)photoAtIndex:(NSUInteger)index; - (id)thumbPhotoAtIndex:(NSUInteger)index; - (UIImage *)imageForPhoto:(id)photo; - (BOOL)photoIsSelectedAtIndex:(NSUInteger)index; - (void)setPhotoSelected:(BOOL)selected atIndex:(NSUInteger)index; - (void)loadAdjacentPhotosIfNecessary:(id)photo; - (void)releaseAllUnderlyingPhotos:(BOOL)preserveCurrent; @end ================================================ FILE: Pod/Classes/MWPhotoProtocol.h ================================================ // // MWPhotoProtocol.h // MWPhotoBrowser // // Created by Michael Waterfall on 02/01/2012. // Copyright (c) 2012 __MyCompanyName__. All rights reserved. // #import // Notifications #define MWPHOTO_LOADING_DID_END_NOTIFICATION @"MWPHOTO_LOADING_DID_END_NOTIFICATION" #define MWPHOTO_PROGRESS_NOTIFICATION @"MWPHOTO_PROGRESS_NOTIFICATION" // If you wish to use your own data models for photo then they must conform // to this protocol. See instructions for details on each method. // Otherwise you can use the MWPhoto object or subclass it yourself to // store more information per photo. // // You can see the MWPhoto class for an example implementation of this protocol // @protocol MWPhoto @required // Return underlying UIImage to be displayed // Return nil if the image is not immediately available (loaded into memory, preferably // already decompressed) and needs to be loaded from a source (cache, file, web, etc) // IMPORTANT: You should *NOT* use this method to initiate // fetching of images from any external of source. That should be handled // in -loadUnderlyingImageAndNotify: which may be called by the photo browser if this // methods returns nil. @property (nonatomic, strong) UIImage *underlyingImage; // Called when the browser has determined the underlying images is not // already loaded into memory but needs it. - (void)loadUnderlyingImageAndNotify; // Fetch the image data from a source and notify when complete. // You must load the image asyncronously (and decompress it for better performance). // It is recommended that you use SDWebImageDecoder to perform the decompression. // See MWPhoto object for an example implementation. // When the underlying UIImage is loaded (or failed to load) you should post the following // notification: // [[NSNotificationCenter defaultCenter] postNotificationName:MWPHOTO_LOADING_DID_END_NOTIFICATION // object:self]; - (void)performLoadUnderlyingImageAndNotify; // This is called when the photo browser has determined the photo data // is no longer needed or there are low memory conditions // You should release any underlying (possibly large and decompressed) image data // as long as the image can be re-loaded (from cache, file, or URL) - (void)unloadUnderlyingImage; @optional // If photo is empty, in which case, don't show loading error icons @property (nonatomic) BOOL emptyImage; // Video @property (nonatomic) BOOL isVideo; - (void)getVideoURL:(void (^)(NSURL *url))completion; // Return a caption string to be displayed over the image // Return nil to display no caption - (NSString *)caption; // Cancel any background loading of image data - (void)cancelAnyLoading; @end ================================================ FILE: Pod/Classes/MWTapDetectingImageView.h ================================================ // // UIImageViewTap.h // Momento // // Created by Michael Waterfall on 04/11/2009. // Copyright 2009 d3i. All rights reserved. // #import @protocol MWTapDetectingImageViewDelegate; @interface MWTapDetectingImageView : UIImageView {} @property (nonatomic, weak) id tapDelegate; @end @protocol MWTapDetectingImageViewDelegate @optional - (void)imageView:(UIImageView *)imageView singleTapDetected:(UITouch *)touch; - (void)imageView:(UIImageView *)imageView doubleTapDetected:(UITouch *)touch; - (void)imageView:(UIImageView *)imageView tripleTapDetected:(UITouch *)touch; @end ================================================ FILE: Pod/Classes/MWTapDetectingImageView.m ================================================ // // UIImageViewTap.m // Momento // // Created by Michael Waterfall on 04/11/2009. // Copyright 2009 d3i. All rights reserved. // #import "MWTapDetectingImageView.h" @implementation MWTapDetectingImageView - (id)initWithFrame:(CGRect)frame { if ((self = [super initWithFrame:frame])) { self.userInteractionEnabled = YES; } return self; } - (id)initWithImage:(UIImage *)image { if ((self = [super initWithImage:image])) { self.userInteractionEnabled = YES; } return self; } - (id)initWithImage:(UIImage *)image highlightedImage:(UIImage *)highlightedImage { if ((self = [super initWithImage:image highlightedImage:highlightedImage])) { self.userInteractionEnabled = YES; } return self; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; NSUInteger tapCount = touch.tapCount; switch (tapCount) { case 1: [self handleSingleTap:touch]; break; case 2: [self handleDoubleTap:touch]; break; case 3: [self handleTripleTap:touch]; break; default: break; } [[self nextResponder] touchesEnded:touches withEvent:event]; } - (void)handleSingleTap:(UITouch *)touch { if ([_tapDelegate respondsToSelector:@selector(imageView:singleTapDetected:)]) [_tapDelegate imageView:self singleTapDetected:touch]; } - (void)handleDoubleTap:(UITouch *)touch { if ([_tapDelegate respondsToSelector:@selector(imageView:doubleTapDetected:)]) [_tapDelegate imageView:self doubleTapDetected:touch]; } - (void)handleTripleTap:(UITouch *)touch { if ([_tapDelegate respondsToSelector:@selector(imageView:tripleTapDetected:)]) [_tapDelegate imageView:self tripleTapDetected:touch]; } @end ================================================ FILE: Pod/Classes/MWTapDetectingView.h ================================================ // // UIViewTap.h // Momento // // Created by Michael Waterfall on 04/11/2009. // Copyright 2009 d3i. All rights reserved. // #import @protocol MWTapDetectingViewDelegate; @interface MWTapDetectingView : UIView {} @property (nonatomic, weak) id tapDelegate; @end @protocol MWTapDetectingViewDelegate @optional - (void)view:(UIView *)view singleTapDetected:(UITouch *)touch; - (void)view:(UIView *)view doubleTapDetected:(UITouch *)touch; - (void)view:(UIView *)view tripleTapDetected:(UITouch *)touch; @end ================================================ FILE: Pod/Classes/MWTapDetectingView.m ================================================ // // UIViewTap.m // Momento // // Created by Michael Waterfall on 04/11/2009. // Copyright 2009 d3i. All rights reserved. // #import "MWTapDetectingView.h" @implementation MWTapDetectingView - (id)init { if ((self = [super init])) { self.userInteractionEnabled = YES; } return self; } - (id)initWithFrame:(CGRect)frame { if ((self = [super initWithFrame:frame])) { self.userInteractionEnabled = YES; } return self; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; NSUInteger tapCount = touch.tapCount; switch (tapCount) { case 1: [self handleSingleTap:touch]; break; case 2: [self handleDoubleTap:touch]; break; case 3: [self handleTripleTap:touch]; break; default: break; } [[self nextResponder] touchesEnded:touches withEvent:event]; } - (void)handleSingleTap:(UITouch *)touch { if ([_tapDelegate respondsToSelector:@selector(view:singleTapDetected:)]) [_tapDelegate view:self singleTapDetected:touch]; } - (void)handleDoubleTap:(UITouch *)touch { if ([_tapDelegate respondsToSelector:@selector(view:doubleTapDetected:)]) [_tapDelegate view:self doubleTapDetected:touch]; } - (void)handleTripleTap:(UITouch *)touch { if ([_tapDelegate respondsToSelector:@selector(view:tripleTapDetected:)]) [_tapDelegate view:self tripleTapDetected:touch]; } @end ================================================ FILE: Pod/Classes/MWZoomingScrollView.h ================================================ // // ZoomingScrollView.h // MWPhotoBrowser // // Created by Michael Waterfall on 14/10/2010. // Copyright 2010 d3i. All rights reserved. // #import #import "MWPhotoProtocol.h" #import "MWTapDetectingImageView.h" #import "MWTapDetectingView.h" @class MWPhotoBrowser, MWPhoto, MWCaptionView; @interface MWZoomingScrollView : UIScrollView { } @property () NSUInteger index; @property (nonatomic) id photo; @property (nonatomic, weak) MWCaptionView *captionView; @property (nonatomic, weak) UIButton *selectedButton; @property (nonatomic, weak) UIButton *playButton; - (id)initWithPhotoBrowser:(MWPhotoBrowser *)browser; - (void)displayImage; - (void)displayImageFailure; - (void)setMaxMinZoomScalesForCurrentBounds; - (void)prepareForReuse; - (BOOL)displayingVideo; - (void)setImageHidden:(BOOL)hidden; @end ================================================ FILE: Pod/Classes/MWZoomingScrollView.m ================================================ // // ZoomingScrollView.m // MWPhotoBrowser // // Created by Michael Waterfall on 14/10/2010. // Copyright 2010 d3i. All rights reserved. // #import #import "MWCommon.h" #import "MWZoomingScrollView.h" #import "MWPhotoBrowser.h" #import "MWPhoto.h" #import "MWPhotoBrowserPrivate.h" #import "UIImage+MWPhotoBrowser.h" // Private methods and properties @interface MWZoomingScrollView () { MWPhotoBrowser __weak *_photoBrowser; MWTapDetectingView *_tapView; // for background taps MWTapDetectingImageView *_photoImageView; DACircularProgressView *_loadingIndicator; UIImageView *_loadingError; } @end @implementation MWZoomingScrollView - (id)initWithPhotoBrowser:(MWPhotoBrowser *)browser { if ((self = [super init])) { // Setup _index = NSUIntegerMax; _photoBrowser = browser; // Tap view for background _tapView = [[MWTapDetectingView alloc] initWithFrame:self.bounds]; _tapView.tapDelegate = self; _tapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; _tapView.backgroundColor = [UIColor blackColor]; [self addSubview:_tapView]; // Image view _photoImageView = [[MWTapDetectingImageView alloc] initWithFrame:CGRectZero]; _photoImageView.tapDelegate = self; _photoImageView.contentMode = UIViewContentModeCenter; _photoImageView.backgroundColor = [UIColor blackColor]; [self addSubview:_photoImageView]; // Loading indicator _loadingIndicator = [[DACircularProgressView alloc] initWithFrame:CGRectMake(140.0f, 30.0f, 40.0f, 40.0f)]; _loadingIndicator.userInteractionEnabled = NO; _loadingIndicator.thicknessRatio = 0.1; _loadingIndicator.roundedCorners = NO; _loadingIndicator.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin; [self addSubview:_loadingIndicator]; // Listen progress notifications [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setProgressFromNotification:) name:MWPHOTO_PROGRESS_NOTIFICATION object:nil]; // Setup self.backgroundColor = [UIColor blackColor]; self.delegate = self; self.showsHorizontalScrollIndicator = NO; self.showsVerticalScrollIndicator = NO; self.decelerationRate = UIScrollViewDecelerationRateFast; self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; } return self; } - (void)dealloc { if ([_photo respondsToSelector:@selector(cancelAnyLoading)]) { [_photo cancelAnyLoading]; } [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)prepareForReuse { [self hideImageFailure]; self.photo = nil; self.captionView = nil; self.selectedButton = nil; self.playButton = nil; _photoImageView.hidden = NO; _photoImageView.image = nil; _index = NSUIntegerMax; } - (BOOL)displayingVideo { return [_photo respondsToSelector:@selector(isVideo)] && _photo.isVideo; } - (void)setImageHidden:(BOOL)hidden { _photoImageView.hidden = hidden; } #pragma mark - Image - (void)setPhoto:(id)photo { // Cancel any loading on old photo if (_photo && photo == nil) { if ([_photo respondsToSelector:@selector(cancelAnyLoading)]) { [_photo cancelAnyLoading]; } } _photo = photo; UIImage *img = [_photoBrowser imageForPhoto:_photo]; if (img) { [self displayImage]; } else { // Will be loading so show loading [self showLoadingIndicator]; } } // Get and display image - (void)displayImage { if (_photo && _photoImageView.image == nil) { // Reset self.maximumZoomScale = 1; self.minimumZoomScale = 1; self.zoomScale = 1; self.contentSize = CGSizeMake(0, 0); // Get image from browser as it handles ordering of fetching UIImage *img = [_photoBrowser imageForPhoto:_photo]; if (img) { // Hide indicator [self hideLoadingIndicator]; // Set image _photoImageView.image = img; _photoImageView.hidden = NO; // Setup photo frame CGRect photoImageViewFrame; photoImageViewFrame.origin = CGPointZero; photoImageViewFrame.size = img.size; _photoImageView.frame = photoImageViewFrame; self.contentSize = photoImageViewFrame.size; // Set zoom to minimum zoom [self setMaxMinZoomScalesForCurrentBounds]; } else { // Show image failure [self displayImageFailure]; } [self setNeedsLayout]; } } // Image failed so just show black! - (void)displayImageFailure { [self hideLoadingIndicator]; _photoImageView.image = nil; // Show if image is not empty if (![_photo respondsToSelector:@selector(emptyImage)] || !_photo.emptyImage) { if (!_loadingError) { _loadingError = [UIImageView new]; _loadingError.image = [UIImage imageForResourcePath:@"MWPhotoBrowser.bundle/ImageError" ofType:@"png" inBundle:[NSBundle bundleForClass:[self class]]]; _loadingError.userInteractionEnabled = NO; _loadingError.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin; [_loadingError sizeToFit]; [self addSubview:_loadingError]; } _loadingError.frame = CGRectMake(floorf((self.bounds.size.width - _loadingError.frame.size.width) / 2.), floorf((self.bounds.size.height - _loadingError.frame.size.height) / 2), _loadingError.frame.size.width, _loadingError.frame.size.height); } } - (void)hideImageFailure { if (_loadingError) { [_loadingError removeFromSuperview]; _loadingError = nil; } } #pragma mark - Loading Progress - (void)setProgressFromNotification:(NSNotification *)notification { dispatch_async(dispatch_get_main_queue(), ^{ NSDictionary *dict = [notification object]; id photoWithProgress = [dict objectForKey:@"photo"]; if (photoWithProgress == self.photo) { float progress = [[dict valueForKey:@"progress"] floatValue]; _loadingIndicator.progress = MAX(MIN(1, progress), 0); } }); } - (void)hideLoadingIndicator { _loadingIndicator.hidden = YES; } - (void)showLoadingIndicator { self.zoomScale = 0; self.minimumZoomScale = 0; self.maximumZoomScale = 0; _loadingIndicator.progress = 0; _loadingIndicator.hidden = NO; [self hideImageFailure]; } #pragma mark - Setup - (CGFloat)initialZoomScaleWithMinScale { CGFloat zoomScale = self.minimumZoomScale; if (_photoImageView && _photoBrowser.zoomPhotosToFill) { // Zoom image to fill if the aspect ratios are fairly similar CGSize boundsSize = self.bounds.size; CGSize imageSize = _photoImageView.image.size; CGFloat boundsAR = boundsSize.width / boundsSize.height; CGFloat imageAR = imageSize.width / imageSize.height; CGFloat xScale = boundsSize.width / imageSize.width; // the scale needed to perfectly fit the image width-wise CGFloat yScale = boundsSize.height / imageSize.height; // the scale needed to perfectly fit the image height-wise // Zooms standard portrait images on a 3.5in screen but not on a 4in screen. if (ABS(boundsAR - imageAR) < 0.17) { zoomScale = MAX(xScale, yScale); // Ensure we don't zoom in or out too far, just in case zoomScale = MIN(MAX(self.minimumZoomScale, zoomScale), self.maximumZoomScale); } } return zoomScale; } - (void)setMaxMinZoomScalesForCurrentBounds { // Reset self.maximumZoomScale = 1; self.minimumZoomScale = 1; self.zoomScale = 1; // Bail if no image if (_photoImageView.image == nil) return; // Reset position _photoImageView.frame = CGRectMake(0, 0, _photoImageView.frame.size.width, _photoImageView.frame.size.height); // Sizes CGSize boundsSize = self.bounds.size; CGSize imageSize = _photoImageView.image.size; // Calculate Min CGFloat xScale = boundsSize.width / imageSize.width; // the scale needed to perfectly fit the image width-wise CGFloat yScale = boundsSize.height / imageSize.height; // the scale needed to perfectly fit the image height-wise CGFloat minScale = MIN(xScale, yScale); // use minimum of these to allow the image to become fully visible // Calculate Max CGFloat maxScale = 3; if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { // Let them go a bit bigger on a bigger screen! maxScale = 4; } // Image is smaller than screen so no zooming! if (xScale >= 1 && yScale >= 1) { minScale = 1.0; } // Set min/max zoom self.maximumZoomScale = maxScale; self.minimumZoomScale = minScale; // Initial zoom self.zoomScale = [self initialZoomScaleWithMinScale]; // If we're zooming to fill then centralise if (self.zoomScale != minScale) { // Centralise self.contentOffset = CGPointMake((imageSize.width * self.zoomScale - boundsSize.width) / 2.0, (imageSize.height * self.zoomScale - boundsSize.height) / 2.0); } // Disable scrolling initially until the first pinch to fix issues with swiping on an initally zoomed in photo self.scrollEnabled = NO; // If it's a video then disable zooming if ([self displayingVideo]) { self.maximumZoomScale = self.zoomScale; self.minimumZoomScale = self.zoomScale; } // Layout [self setNeedsLayout]; } #pragma mark - Layout - (void)layoutSubviews { // Update tap view frame _tapView.frame = self.bounds; // Position indicators (centre does not seem to work!) if (!_loadingIndicator.hidden) _loadingIndicator.frame = CGRectMake(floorf((self.bounds.size.width - _loadingIndicator.frame.size.width) / 2.), floorf((self.bounds.size.height - _loadingIndicator.frame.size.height) / 2), _loadingIndicator.frame.size.width, _loadingIndicator.frame.size.height); if (_loadingError) _loadingError.frame = CGRectMake(floorf((self.bounds.size.width - _loadingError.frame.size.width) / 2.), floorf((self.bounds.size.height - _loadingError.frame.size.height) / 2), _loadingError.frame.size.width, _loadingError.frame.size.height); // Super [super layoutSubviews]; // Center the image as it becomes smaller than the size of the screen CGSize boundsSize = self.bounds.size; CGRect frameToCenter = _photoImageView.frame; // Horizontally if (frameToCenter.size.width < boundsSize.width) { frameToCenter.origin.x = floorf((boundsSize.width - frameToCenter.size.width) / 2.0); } else { frameToCenter.origin.x = 0; } // Vertically if (frameToCenter.size.height < boundsSize.height) { frameToCenter.origin.y = floorf((boundsSize.height - frameToCenter.size.height) / 2.0); } else { frameToCenter.origin.y = 0; } // Center if (!CGRectEqualToRect(_photoImageView.frame, frameToCenter)) _photoImageView.frame = frameToCenter; } #pragma mark - UIScrollViewDelegate - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView { return _photoImageView; } - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { [_photoBrowser cancelControlHiding]; } - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view { self.scrollEnabled = YES; // reset [_photoBrowser cancelControlHiding]; } - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { [_photoBrowser hideControlsAfterDelay]; } - (void)scrollViewDidZoom:(UIScrollView *)scrollView { [self setNeedsLayout]; [self layoutIfNeeded]; } #pragma mark - Tap Detection - (void)handleSingleTap:(CGPoint)touchPoint { [_photoBrowser performSelector:@selector(toggleControls) withObject:nil afterDelay:0.2]; } - (void)handleDoubleTap:(CGPoint)touchPoint { // Dont double tap to zoom if showing a video if ([self displayingVideo]) { return; } // Cancel any single tap handling [NSObject cancelPreviousPerformRequestsWithTarget:_photoBrowser]; // Zoom if (self.zoomScale != self.minimumZoomScale && self.zoomScale != [self initialZoomScaleWithMinScale]) { // Zoom out [self setZoomScale:self.minimumZoomScale animated:YES]; } else { // Zoom in to twice the size CGFloat newZoomScale = ((self.maximumZoomScale + self.minimumZoomScale) / 2); CGFloat xsize = self.bounds.size.width / newZoomScale; CGFloat ysize = self.bounds.size.height / newZoomScale; [self zoomToRect:CGRectMake(touchPoint.x - xsize/2, touchPoint.y - ysize/2, xsize, ysize) animated:YES]; } // Delay controls [_photoBrowser hideControlsAfterDelay]; } // Image View - (void)imageView:(UIImageView *)imageView singleTapDetected:(UITouch *)touch { [self handleSingleTap:[touch locationInView:imageView]]; } - (void)imageView:(UIImageView *)imageView doubleTapDetected:(UITouch *)touch { [self handleDoubleTap:[touch locationInView:imageView]]; } // Background View - (void)view:(UIView *)view singleTapDetected:(UITouch *)touch { // Translate touch location to image view location CGFloat touchX = [touch locationInView:view].x; CGFloat touchY = [touch locationInView:view].y; touchX *= 1/self.zoomScale; touchY *= 1/self.zoomScale; touchX += self.contentOffset.x; touchY += self.contentOffset.y; [self handleSingleTap:CGPointMake(touchX, touchY)]; } - (void)view:(UIView *)view doubleTapDetected:(UITouch *)touch { // Translate touch location to image view location CGFloat touchX = [touch locationInView:view].x; CGFloat touchY = [touch locationInView:view].y; touchX *= 1/self.zoomScale; touchY *= 1/self.zoomScale; touchX += self.contentOffset.x; touchY += self.contentOffset.y; [self handleDoubleTap:CGPointMake(touchX, touchY)]; } @end ================================================ FILE: Pod/Classes/UIImage+MWPhotoBrowser.h ================================================ // // UIImage+MWPhotoBrowser.h // Pods // // Created by Michael Waterfall on 05/07/2015. // // #import @interface UIImage (MWPhotoBrowser) + (UIImage *)imageForResourcePath:(NSString *)path ofType:(NSString *)type inBundle:(NSBundle *)bundle; + (UIImage *)clearImageWithSize:(CGSize)size; @end ================================================ FILE: Pod/Classes/UIImage+MWPhotoBrowser.m ================================================ // // UIImage+MWPhotoBrowser.m // Pods // // Created by Michael Waterfall on 05/07/2015. // // #import "UIImage+MWPhotoBrowser.h" @implementation UIImage (MWPhotoBrowser) + (UIImage *)imageForResourcePath:(NSString *)path ofType:(NSString *)type inBundle:(NSBundle *)bundle { return [UIImage imageWithContentsOfFile:[bundle pathForResource:path ofType:type]]; } + (UIImage *)clearImageWithSize:(CGSize)size { UIGraphicsBeginImageContextWithOptions(size, NO, [UIScreen mainScreen].scale); UIImage *blank = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return blank; } @end ================================================ FILE: README.md ================================================ # MWPhotoBrowser [![Version](https://img.shields.io/cocoapods/v/MWPhotoBrowser.svg?style=flat)](http://cocoapods.org/pods/MWPhotoBrowser) [![License](https://img.shields.io/cocoapods/l/MWPhotoBrowser.svg?style=flat)](http://cocoapods.org/pods/MWPhotoBrowser) [![Platform](https://img.shields.io/cocoapods/p/MWPhotoBrowser.svg?style=flat)](http://cocoapods.org/pods/MWPhotoBrowser) [![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=mwaterfall&url=https://github.com/mwaterfall/MWPhotoBrowser&title=MWPhotoBrowser&language=&tags=github&category=software) ## A simple iOS photo and video browser with optional grid view, captions and selections. MWPhotoBrowser can display one or more images or videos by providing either `UIImage` objects, `PHAsset` objects, or URLs to library assets, web images/videos or local files. The photo browser handles the downloading and caching of photos from the web seamlessly. Photos can be zoomed and panned, and optional (customisable) captions can be displayed. The browser can also be used to allow the user to select one or more photos using either the grid or main image view. [![Alt][screenshot1_thumb]][screenshot1]    [![Alt][screenshot2_thumb]][screenshot2]    [![Alt][screenshot3_thumb]][screenshot3]    [![Alt][screenshot4_thumb]][screenshot4]    [![Alt][screenshot5_thumb]][screenshot5]    [![Alt][screenshot6_thumb]][screenshot6] [screenshot1_thumb]: https://raw.github.com/mwaterfall/MWPhotoBrowser/master/Screenshots/MWPhotoBrowser1t.png [screenshot1]: https://raw.github.com/mwaterfall/MWPhotoBrowser/master/Screenshots/MWPhotoBrowser1.png [screenshot2_thumb]: https://raw.github.com/mwaterfall/MWPhotoBrowser/master/Screenshots/MWPhotoBrowser2t.png [screenshot2]: https://raw.github.com/mwaterfall/MWPhotoBrowser/master/Screenshots/MWPhotoBrowser2.png [screenshot3_thumb]: https://raw.github.com/mwaterfall/MWPhotoBrowser/master/Screenshots/MWPhotoBrowser3t.png [screenshot3]: https://raw.github.com/mwaterfall/MWPhotoBrowser/master/Screenshots/MWPhotoBrowser3.png [screenshot4_thumb]: https://raw.github.com/mwaterfall/MWPhotoBrowser/master/Screenshots/MWPhotoBrowser4t.png [screenshot4]: https://raw.github.com/mwaterfall/MWPhotoBrowser/master/Screenshots/MWPhotoBrowser4.png [screenshot5_thumb]: https://raw.github.com/mwaterfall/MWPhotoBrowser/master/Screenshots/MWPhotoBrowser5t.png [screenshot5]: https://raw.github.com/mwaterfall/MWPhotoBrowser/master/Screenshots/MWPhotoBrowser5.png [screenshot6_thumb]: https://raw.github.com/mwaterfall/MWPhotoBrowser/master/Screenshots/MWPhotoBrowser6t.png [screenshot6]: https://raw.github.com/mwaterfall/MWPhotoBrowser/master/Screenshots/MWPhotoBrowser6.png Works on iOS 7+. All strings are localisable so they can be used in apps that support multiple languages. ## Usage MWPhotoBrowser is designed to be presented within a navigation controller. Simply set the delegate (which must conform to `MWPhotoBrowserDelegate`) and implement the 2 required delegate methods to provide the photo browser with the data in the form of `MWPhoto` objects. You can create an `MWPhoto` object by providing a `UIImage` object, `PHAsset` object, or a URL containing the path to a file, an image online or an asset from the asset library. `MWPhoto` objects handle caching, file management, downloading of web images, and various optimisations for you. If however you would like to use your own data model to represent photos you can simply ensure your model conforms to the `MWPhoto` protocol. You can then handle the management of caching, downloads, etc, yourself. More information on this can be found in `MWPhotoProtocol.h`. See the code snippet below for an example of how to implement the photo browser. There is also a simple demo app within the project. ```obj-c // Create array of MWPhoto objects self.photos = [NSMutableArray array]; // Add photos [photos addObject:[MWPhoto photoWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"photo2l" ofType:@"jpg"]]]]; [photos addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3629/3339128908_7aecabc34b.jpg"]]]; [photos addObject:[MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3590/3329114220_5fbc5bc92b.jpg"]]]; // Add video with poster photo MWPhoto *video = [MWPhoto photoWithURL:[NSURL URLWithString:@"https://scontent.cdninstagram.com/hphotos-xpt1/t51.2885-15/e15/11192696_824079697688618_1761661_n.jpg"]]; video.videoURL = [[NSURL alloc] initWithString:@"https://scontent.cdninstagram.com/hphotos-xpa1/t50.2886-16/11200303_1440130956287424_1714699187_n.mp4"]; [photos addObject:video]; // Create browser (must be done each time photo browser is // displayed. Photo browser objects cannot be re-used) MWPhotoBrowser *browser = [[MWPhotoBrowser alloc] initWithDelegate:self]; // Set options browser.displayActionButton = YES; // Show action button to allow sharing, copying, etc (defaults to YES) browser.displayNavArrows = NO; // Whether to display left and right nav arrows on toolbar (defaults to NO) browser.displaySelectionButtons = NO; // Whether selection buttons are shown on each image (defaults to NO) browser.zoomPhotosToFill = YES; // Images that almost fill the screen will be initially zoomed to fill (defaults to YES) browser.alwaysShowControls = NO; // Allows to control whether the bars and controls are always visible or whether they fade away to show the photo full (defaults to NO) browser.enableGrid = YES; // Whether to allow the viewing of all the photo thumbnails on a grid (defaults to YES) browser.startOnGrid = NO; // Whether to start on the grid of thumbnails instead of the first photo (defaults to NO) browser.autoPlayOnAppear = NO; // Auto-play first video // Customise selection images to change colours if required browser.customImageSelectedIconName = @"ImageSelected.png"; browser.customImageSelectedSmallIconName = @"ImageSelectedSmall.png"; // Optionally set the current visible photo before displaying [browser setCurrentPhotoIndex:1]; // Present [self.navigationController pushViewController:browser animated:YES]; // Manipulate [browser showNextPhotoAnimated:YES]; [browser showPreviousPhotoAnimated:YES]; [browser setCurrentPhotoIndex:10]; ``` Then respond to the required delegate methods: ```obj-c - (NSUInteger)numberOfPhotosInPhotoBrowser:(MWPhotoBrowser *)photoBrowser { return self.photos.count; } - (id )photoBrowser:(MWPhotoBrowser *)photoBrowser photoAtIndex:(NSUInteger)index { if (index < self.photos.count) { return [self.photos objectAtIndex:index]; } return nil; } ``` You can present the browser modally simply by wrapping it in a new navigation controller and presenting that. The demo app allows you to toggle between the two presentation types. ### Videos You can represent videos in MWPhoto objects by providing a standard MWPhoto image object with a `videoURL`. You can also use a `PHAsset` object or a URL to an assets library video. ```obj-c // Video with URL including poster photo MWPhoto *video = [MWPhoto photoWithURL:[NSURL URLWithString:@"https://scontent.cdninstagram.com/hphotos-xpt1/t51.2885-15/e15/11192696_824079697688618_1761661_n.jpg"]]; video.videoURL = [NSURL URLWithString:@"https://scontent.cdninstagram.com/hphotos-xpa1/t50.2886-16/11200303_1440130956287424_1714699187_n.mp4"]; // Video with PHAsset MWPhoto *video = [MWPhoto photoWithAsset:asset targetSize:[UIScreen mainScreen].bounds.size]; // Example sizing // Video with ALAsset MWPhoto *video = [MWPhoto photoWithURL:asset.defaultRepresentation.url]; if ([asset valueForProperty:ALAssetPropertyType] == ALAssetTypeVideo) { photo.videoURL = asset.defaultRepresentation.url; } // Video with no poster photo MWPhoto *video = [MWPhoto videoWithURL:[NSURL URLWithString:@"https://scontent.cdninstagram.com/hphotos-xfa1/t50.2886-16/11237510_945154435524423_2137519922_n.mp4"]]; // Video grid thumbnail MWPhoto *videoThumb = [MWPhoto photoWithURL:[NSURL URLWithString:@"https://scontent.cdninstagram.com/hphotos-xaf1/t51.2885-15/s150x150/e15/11240463_963135443745570_1519872157_n.jpg"]]; videoThumb.isVideo = YES; // Video grid thumbnail for video with no poster photo MWPhoto *videoThumb = [MWPhoto new]; videoThumb.isVideo = YES; ``` ### Grid In order to properly show the grid of thumbnails, you must ensure the property `enableGrid` is set to `YES`, and implement the following delegate method: ```obj-c - (id )photoBrowser:(MWPhotoBrowser *)photoBrowser thumbPhotoAtIndex:(NSUInteger)index; ``` The photo browser can also start on the grid by enabling the `startOnGrid` property. ### Actions By default, if the action button is visible then the image (and caption if it exists) are sent to a UIActivityViewController. You can provide a custom action by implementing the following delegate method: ```obj-c - (void)photoBrowser:(MWPhotoBrowser *)photoBrowser actionButtonPressedForPhotoAtIndex:(NSUInteger)index { // Do your thing! } ``` ### Photo Captions Photo captions can be displayed simply by setting the `caption` property on specific photos: ```obj-c MWPhoto *photo = [MWPhoto photoWithURL:[NSURL URLWithString:@"http://farm4.static.flickr.com/3629/3339128908_7aecabc34b.jpg"]]; photo.caption = @"Campervan"; ``` No caption will be displayed if the caption property is not set. #### Custom Captions By default, the caption is a simple black transparent view with a label displaying the photo's caption in white. If you want to implement your own caption view, follow these steps: 1. Optionally use a subclass of `MWPhoto` for your photos so you can store more data than a simple caption string. 2. Subclass `MWCaptionView` and override `-setupCaption` and `-sizeThatFits:` (and any other UIView methods you see fit) to layout your own view and set it's size. More information on this can be found in `MWCaptionView.h` 3. Implement the `-photoBrowser:captionViewForPhotoAtIndex:` MWPhotoBrowser delegate method (shown below). Example delegate method for custom caption view: ```obj-c - (MWCaptionView *)photoBrowser:(MWPhotoBrowser *)photoBrowser captionViewForPhotoAtIndex:(NSUInteger)index { MWPhoto *photo = [self.photos objectAtIndex:index]; MyMWCaptionViewSubclass *captionView = [[MyMWCaptionViewSubclass alloc] initWithPhoto:photo]; return captionView; } ``` #### Selections The photo browser can display check boxes allowing the user to select one or more of the photos. To use this feature, simply enable the `displaySelectionButtons` property, and implement the following delegate methods: ```obj-c - (BOOL)photoBrowser:(MWPhotoBrowser *)photoBrowser isPhotoSelectedAtIndex:(NSUInteger)index { return [[_selections objectAtIndex:index] boolValue]; } - (void)photoBrowser:(MWPhotoBrowser *)photoBrowser photoAtIndex:(NSUInteger)index selectedChanged:(BOOL)selected { [_selections replaceObjectAtIndex:index withObject:[NSNumber numberWithBool:selected]]; } ``` ## Installation MWPhotoBrowser is available through [CocoaPods](http://cocoapods.org). To install it, simply add the following line to your Podfile: ```ruby pod "MWPhotoBrowser" ``` ## Usage To run the example project, clone the repo, and run `pod install` from the Example directory first. Then import the photo browser into your source files (or into your bridging header if you're using with Swift and not using frameworks with Cocoapods): ```obj-c #import "MWPhotoBrowser.h" ``` If you are using Swift and frameworks, then you can just import the browser into your Swift source file: ```swift import MWPhotoBrowser ``` ## Author Michael Waterfall, michael@d3i.com ## License MWPhotoBrowser is available under the MIT license. See the LICENSE file for more info. ## Notes Demo photos kindly provided by [Oliver Waters]().