├── PhotoBrowserDemo ├── .gitignore ├── PhotoBrowserDemo copy-Info.plist ├── PhotoBrowserDemo.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcuserdata │ │ │ └── admin.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ ├── admin.xcuserdatad │ │ └── xcschemes │ │ │ ├── PhotoBrowserCustomCoreDemo.xcscheme │ │ │ ├── PhotoBrowserDemo.xcscheme │ │ │ └── xcschememanagement.plist │ │ └── heroims.xcuserdatad │ │ └── xcschemes │ │ ├── PhotoBrowserCustomCoreDemo.xcscheme │ │ ├── PhotoBrowserDemo.xcscheme │ │ └── xcschememanagement.plist ├── PhotoBrowserDemo.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ ├── admin.xcuserdatad │ │ ├── UserInterfaceState.xcuserstate │ │ └── xcdebugger │ │ │ └── Breakpoints_v2.xcbkptlist │ │ └── heroims.xcuserdatad │ │ ├── UserInterfaceState.xcuserstate │ │ └── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist ├── PhotoBrowserDemo │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Custom Images │ │ ├── PhotoBrowser_customArrowLeft.png │ │ ├── PhotoBrowser_customArrowLeft@2x.png │ │ ├── PhotoBrowser_customArrowLeftSelected.png │ │ ├── PhotoBrowser_customArrowLeftSelected@2x.png │ │ ├── PhotoBrowser_customArrowRight.png │ │ ├── PhotoBrowser_customArrowRight@2x.png │ │ ├── PhotoBrowser_customArrowRightSelected.png │ │ ├── PhotoBrowser_customArrowRightSelected@2x.png │ │ ├── PhotoBrowser_customDoneButton.png │ │ └── PhotoBrowser_customDoneButton@2x.png │ ├── Info.plist │ ├── Photos │ │ ├── photo1l.jpg │ │ ├── photo1m.jpg │ │ ├── photo2l.jpg │ │ ├── photo2m.jpg │ │ ├── photo3l.jpg │ │ ├── photo3m.jpg │ │ ├── photo4l.jpg │ │ ├── photo4m.jpg │ │ └── photo5l.gif │ ├── ViewController.h │ ├── ViewController.m │ ├── ZYQPhotoBrowser+Custom.h │ ├── ZYQPhotoBrowser+Custom.m │ └── main.m ├── PhotoBrowserDemoTests │ ├── Info.plist │ └── PhotoBrowserDemoTests.m ├── PhotoBrowserDemoUITests │ ├── Info.plist │ └── PhotoBrowserDemoUITests.m ├── Podfile └── Podfile.lock ├── README.md ├── ZYQPhotoBrowser-NoDependency.podspec ├── ZYQPhotoBrowser.podspec └── ZYQPhotoBrowser ├── ZYQCaptionView.h ├── ZYQCaptionView.m ├── ZYQPBConstants.h ├── ZYQPBLocalizations.bundle ├── de.lproj │ └── Localizable.strings ├── en.lproj │ └── Localizable.strings ├── es.lproj │ └── Localizable.strings ├── fr.lproj │ └── Localizable.strings ├── ja.lproj │ └── Localizable.strings ├── nl.lproj │ └── Localizable.strings ├── pt.lproj │ └── Localizable.strings ├── ru.lproj │ └── Localizable.strings └── zh-Hans.lproj │ └── Localizable.strings ├── ZYQPhoto.h ├── ZYQPhoto.m ├── ZYQPhotoBrowser.bundle └── images │ ├── IDMPhotoBrowser_arrowLeft.png │ ├── IDMPhotoBrowser_arrowLeft@2x.png │ ├── IDMPhotoBrowser_arrowRight.png │ └── IDMPhotoBrowser_arrowRight@2x.png ├── ZYQPhotoBrowser.h ├── ZYQPhotoBrowser.m ├── ZYQPhotoProtocol.h ├── ZYQTapDetectingImageView.h ├── ZYQTapDetectingImageView.m ├── ZYQTapDetectingView.h ├── ZYQTapDetectingView.m ├── ZYQZoomingScrollView.h └── ZYQZoomingScrollView.m /PhotoBrowserDemo/.gitignore: -------------------------------------------------------------------------------- 1 | /Pods 2 | /Demo.xcworkspace 3 | /UserInterfaceState.xcuserstate 4 | -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo copy-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo.xcodeproj/project.xcworkspace/xcuserdata/admin.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heroims/ZYQPhotoBrowser/0bfa3ce3a5cf5ce4a452bf1c5d5335a066a4a367/PhotoBrowserDemo/PhotoBrowserDemo.xcodeproj/project.xcworkspace/xcuserdata/admin.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo.xcodeproj/xcuserdata/admin.xcuserdatad/xcschemes/PhotoBrowserCustomCoreDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo.xcodeproj/xcuserdata/admin.xcuserdatad/xcschemes/PhotoBrowserDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 61 | 62 | 63 | 64 | 74 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 95 | 101 | 102 | 103 | 104 | 106 | 107 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo.xcodeproj/xcuserdata/admin.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | PhotoBrowserCustomCoreDemo.xcscheme 8 | 9 | orderHint 10 | 7 11 | 12 | PhotoBrowserDemo.xcscheme 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | 84549B181EFA48D600888AD8 21 | 22 | primary 23 | 24 | 25 | 84549B311EFA48D700888AD8 26 | 27 | primary 28 | 29 | 30 | 84549B3C1EFA48D700888AD8 31 | 32 | primary 33 | 34 | 35 | 84549B921EFBC1A600888AD8 36 | 37 | primary 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo.xcodeproj/xcuserdata/heroims.xcuserdatad/xcschemes/PhotoBrowserCustomCoreDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo.xcodeproj/xcuserdata/heroims.xcuserdatad/xcschemes/PhotoBrowserDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 61 | 62 | 63 | 64 | 74 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 95 | 101 | 102 | 103 | 104 | 106 | 107 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo.xcodeproj/xcuserdata/heroims.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | PhotoBrowserCustomCoreDemo.xcscheme 8 | 9 | orderHint 10 | 1 11 | 12 | PhotoBrowserDemo.xcscheme 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | 84549B181EFA48D600888AD8 21 | 22 | primary 23 | 24 | 25 | 84549B311EFA48D700888AD8 26 | 27 | primary 28 | 29 | 30 | 84549B3C1EFA48D700888AD8 31 | 32 | primary 33 | 34 | 35 | 84549B921EFBC1A600888AD8 36 | 37 | primary 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo.xcworkspace/xcuserdata/admin.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heroims/ZYQPhotoBrowser/0bfa3ce3a5cf5ce4a452bf1c5d5335a066a4a367/PhotoBrowserDemo/PhotoBrowserDemo.xcworkspace/xcuserdata/admin.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo.xcworkspace/xcuserdata/admin.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 20 | 21 | 22 | 24 | 36 | 37 | 38 | 40 | 52 | 53 | 54 | 56 | 68 | 69 | 70 | 72 | 84 | 85 | 86 | 88 | 100 | 101 | 102 | 104 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo.xcworkspace/xcuserdata/heroims.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heroims/ZYQPhotoBrowser/0bfa3ce3a5cf5ce4a452bf1c5d5335a066a4a367/PhotoBrowserDemo/PhotoBrowserDemo.xcworkspace/xcuserdata/heroims.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo.xcworkspace/xcuserdata/heroims.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // PhotoBrowserDemo 4 | // 5 | // Created by admin on 2017/6/21. 6 | // Copyright © 2017年 admin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // PhotoBrowserDemo 4 | // 5 | // Created by admin on 2017/6/21. 6 | // Copyright © 2017年 admin. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | 24 | - (void)applicationWillResignActive:(UIApplication *)application { 25 | // 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. 26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 27 | } 28 | 29 | 30 | - (void)applicationDidEnterBackground:(UIApplication *)application { 31 | // 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. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | 36 | - (void)applicationWillEnterForeground:(UIApplication *)application { 37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | 41 | - (void)applicationDidBecomeActive:(UIApplication *)application { 42 | // 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. 43 | } 44 | 45 | 46 | - (void)applicationWillTerminate:(UIApplication *)application { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | } 43 | ], 44 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo/Custom Images/PhotoBrowser_customArrowLeft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heroims/ZYQPhotoBrowser/0bfa3ce3a5cf5ce4a452bf1c5d5335a066a4a367/PhotoBrowserDemo/PhotoBrowserDemo/Custom Images/PhotoBrowser_customArrowLeft.png -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo/Custom Images/PhotoBrowser_customArrowLeft@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heroims/ZYQPhotoBrowser/0bfa3ce3a5cf5ce4a452bf1c5d5335a066a4a367/PhotoBrowserDemo/PhotoBrowserDemo/Custom Images/PhotoBrowser_customArrowLeft@2x.png -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo/Custom Images/PhotoBrowser_customArrowLeftSelected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heroims/ZYQPhotoBrowser/0bfa3ce3a5cf5ce4a452bf1c5d5335a066a4a367/PhotoBrowserDemo/PhotoBrowserDemo/Custom Images/PhotoBrowser_customArrowLeftSelected.png -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo/Custom Images/PhotoBrowser_customArrowLeftSelected@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heroims/ZYQPhotoBrowser/0bfa3ce3a5cf5ce4a452bf1c5d5335a066a4a367/PhotoBrowserDemo/PhotoBrowserDemo/Custom Images/PhotoBrowser_customArrowLeftSelected@2x.png -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo/Custom Images/PhotoBrowser_customArrowRight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heroims/ZYQPhotoBrowser/0bfa3ce3a5cf5ce4a452bf1c5d5335a066a4a367/PhotoBrowserDemo/PhotoBrowserDemo/Custom Images/PhotoBrowser_customArrowRight.png -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo/Custom Images/PhotoBrowser_customArrowRight@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heroims/ZYQPhotoBrowser/0bfa3ce3a5cf5ce4a452bf1c5d5335a066a4a367/PhotoBrowserDemo/PhotoBrowserDemo/Custom Images/PhotoBrowser_customArrowRight@2x.png -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo/Custom Images/PhotoBrowser_customArrowRightSelected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heroims/ZYQPhotoBrowser/0bfa3ce3a5cf5ce4a452bf1c5d5335a066a4a367/PhotoBrowserDemo/PhotoBrowserDemo/Custom Images/PhotoBrowser_customArrowRightSelected.png -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo/Custom Images/PhotoBrowser_customArrowRightSelected@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heroims/ZYQPhotoBrowser/0bfa3ce3a5cf5ce4a452bf1c5d5335a066a4a367/PhotoBrowserDemo/PhotoBrowserDemo/Custom Images/PhotoBrowser_customArrowRightSelected@2x.png -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo/Custom Images/PhotoBrowser_customDoneButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heroims/ZYQPhotoBrowser/0bfa3ce3a5cf5ce4a452bf1c5d5335a066a4a367/PhotoBrowserDemo/PhotoBrowserDemo/Custom Images/PhotoBrowser_customDoneButton.png -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo/Custom Images/PhotoBrowser_customDoneButton@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heroims/ZYQPhotoBrowser/0bfa3ce3a5cf5ce4a452bf1c5d5335a066a4a367/PhotoBrowserDemo/PhotoBrowserDemo/Custom Images/PhotoBrowser_customDoneButton@2x.png -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo/Photos/photo1l.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heroims/ZYQPhotoBrowser/0bfa3ce3a5cf5ce4a452bf1c5d5335a066a4a367/PhotoBrowserDemo/PhotoBrowserDemo/Photos/photo1l.jpg -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo/Photos/photo1m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heroims/ZYQPhotoBrowser/0bfa3ce3a5cf5ce4a452bf1c5d5335a066a4a367/PhotoBrowserDemo/PhotoBrowserDemo/Photos/photo1m.jpg -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo/Photos/photo2l.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heroims/ZYQPhotoBrowser/0bfa3ce3a5cf5ce4a452bf1c5d5335a066a4a367/PhotoBrowserDemo/PhotoBrowserDemo/Photos/photo2l.jpg -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo/Photos/photo2m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heroims/ZYQPhotoBrowser/0bfa3ce3a5cf5ce4a452bf1c5d5335a066a4a367/PhotoBrowserDemo/PhotoBrowserDemo/Photos/photo2m.jpg -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo/Photos/photo3l.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heroims/ZYQPhotoBrowser/0bfa3ce3a5cf5ce4a452bf1c5d5335a066a4a367/PhotoBrowserDemo/PhotoBrowserDemo/Photos/photo3l.jpg -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo/Photos/photo3m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heroims/ZYQPhotoBrowser/0bfa3ce3a5cf5ce4a452bf1c5d5335a066a4a367/PhotoBrowserDemo/PhotoBrowserDemo/Photos/photo3m.jpg -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo/Photos/photo4l.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heroims/ZYQPhotoBrowser/0bfa3ce3a5cf5ce4a452bf1c5d5335a066a4a367/PhotoBrowserDemo/PhotoBrowserDemo/Photos/photo4l.jpg -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo/Photos/photo4m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heroims/ZYQPhotoBrowser/0bfa3ce3a5cf5ce4a452bf1c5d5335a066a4a367/PhotoBrowserDemo/PhotoBrowserDemo/Photos/photo4m.jpg -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo/Photos/photo5l.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heroims/ZYQPhotoBrowser/0bfa3ce3a5cf5ce4a452bf1c5d5335a066a4a367/PhotoBrowserDemo/PhotoBrowserDemo/Photos/photo5l.gif -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // PhotoBrowserDemo 4 | // 5 | // Created by admin on 2017/6/21. 6 | // Copyright © 2017年 admin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UITableViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // PhotoBrowserDemo 4 | // 5 | // Created by admin on 2017/6/21. 6 | // Copyright © 2017年 admin. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | 11 | #import "ZYQPhotoBrowser.h" 12 | 13 | #ifdef CUSTOM 14 | #import "ZYQPhotoBrowser+Custom.h" 15 | #import "YYWebImage.h" 16 | 17 | @interface BackgroudBlurView : UIView 18 | 19 | @property(nonatomic,strong)UIImageView *imgView; 20 | @property(nonatomic,strong)UIVisualEffectView *maskView; 21 | 22 | @end 23 | 24 | @implementation BackgroudBlurView 25 | 26 | -(instancetype)init{ 27 | if (self=[super init]) { 28 | [self addSubview:self.imgView]; 29 | [self addSubview:self.maskView]; 30 | } 31 | return self; 32 | } 33 | 34 | -(void)layoutSubviews{ 35 | [super layoutSubviews]; 36 | 37 | self.imgView.frame=self.bounds; 38 | self.maskView.frame=self.bounds; 39 | } 40 | 41 | -(UIImageView *)imgView{ 42 | if (_imgView==nil) { 43 | _imgView=[[UIImageView alloc] init]; 44 | } 45 | return _imgView; 46 | } 47 | 48 | -(UIVisualEffectView *)maskView{ 49 | if (_maskView==nil) { 50 | UIBlurEffect *beffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleExtraLight]; 51 | 52 | _maskView = [[UIVisualEffectView alloc]initWithEffect:beffect]; 53 | } 54 | return _maskView; 55 | } 56 | 57 | @end 58 | #else 59 | #endif 60 | 61 | @interface ViewController () 62 | 63 | @end 64 | 65 | @implementation ViewController 66 | 67 | - (void)viewDidLoad { 68 | [super viewDidLoad]; 69 | // Do any additional setup after loading the view, typically from a nib. 70 | #ifdef CUSTOM 71 | self.title=@"Custom"; 72 | #else 73 | self.title=@"Default"; 74 | #endif 75 | 76 | UIView *tableViewFooter=[[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 426 * 0.9 + 40)]; 77 | 78 | UIButton *buttonWithImageOnScreen1=[[UIButton alloc] initWithFrame:CGRectMake(15, 0, 640/3 * 0.9, 426/2 * 0.9)]; 79 | buttonWithImageOnScreen1.tag = 101; 80 | buttonWithImageOnScreen1.adjustsImageWhenHighlighted = false; 81 | [buttonWithImageOnScreen1 setImage:[UIImage imageNamed:@"photo1m.jpg"] forState:UIControlStateNormal]; 82 | buttonWithImageOnScreen1.imageView.contentMode=UIViewContentModeScaleToFill; 83 | buttonWithImageOnScreen1.backgroundColor = [UIColor blackColor]; 84 | [buttonWithImageOnScreen1 addTarget:self action:@selector(buttonWithImageOnScreenPressed:) forControlEvents:UIControlEventTouchUpInside]; 85 | [tableViewFooter addSubview:buttonWithImageOnScreen1]; 86 | 87 | 88 | UIButton *buttonWithImageOnScreen2=[[UIButton alloc] initWithFrame:CGRectMake(15, 426/2 * 0.9 + 20, 640/3 * 0.9, 426/2 * 0.9)]; 89 | buttonWithImageOnScreen2.tag = 102; 90 | buttonWithImageOnScreen2.adjustsImageWhenHighlighted = false; 91 | [buttonWithImageOnScreen2 setImage:[UIImage imageNamed:@"photo3m.jpg"] forState:UIControlStateNormal]; 92 | buttonWithImageOnScreen2.imageView.contentMode=UIViewContentModeScaleToFill; 93 | buttonWithImageOnScreen2.backgroundColor = [UIColor blackColor]; 94 | [buttonWithImageOnScreen2 addTarget:self action:@selector(buttonWithImageOnScreenPressed:) forControlEvents:UIControlEventTouchUpInside]; 95 | [tableViewFooter addSubview:buttonWithImageOnScreen2]; 96 | 97 | self.tableView.tableFooterView = tableViewFooter; 98 | 99 | } 100 | 101 | 102 | - (void)didReceiveMemoryWarning { 103 | [super didReceiveMemoryWarning]; 104 | // Dispose of any resources that can be recreated. 105 | } 106 | 107 | -(void)buttonWithImageOnScreenPressed:(UIButton*)sender{ 108 | NSMutableArray *photos=[[NSMutableArray alloc] init]; 109 | 110 | ZYQPhoto *photo=nil; 111 | 112 | if (sender.tag==101) { 113 | NSString *path_photo1l=[[NSBundle mainBundle] pathForResource:@"photo1l" ofType:@"jpg"]; 114 | 115 | photo=[[ZYQPhoto alloc] initWithFilePath:path_photo1l]; 116 | photo.caption=@"Grotto of the Madonna"; 117 | [photos addObject:photo]; 118 | } 119 | 120 | 121 | 122 | NSString *path_photo3l=[[NSBundle mainBundle] pathForResource:@"photo3l" ofType:@"jpg"]; 123 | 124 | photo=[[ZYQPhoto alloc] initWithFilePath:path_photo3l]; 125 | photo.caption=@"York Floods"; 126 | [photos addObject:photo]; 127 | 128 | NSString *path_photo2l=[[NSBundle mainBundle] pathForResource:@"photo2l" ofType:@"jpg"]; 129 | 130 | photo=[[ZYQPhoto alloc] initWithFilePath:path_photo2l]; 131 | photo.caption=@"The London Eye is a giant Ferris wheel situated on the banks of the River Thames, in London, England."; 132 | [photos addObject:photo]; 133 | 134 | NSString *path_photo4l=[[NSBundle mainBundle] pathForResource:@"photo4l" ofType:@"jpg"]; 135 | 136 | photo=[[ZYQPhoto alloc] initWithFilePath:path_photo4l]; 137 | photo.caption=@"Campervan"; 138 | [photos addObject:photo]; 139 | 140 | NSString *path_photo5l=[[NSBundle mainBundle] pathForResource:@"photo5l" ofType:@"gif"]; 141 | 142 | photo=[[ZYQPhoto alloc] initWithFilePath:path_photo5l]; 143 | photo.caption=@"Gif"; 144 | [photos addObject:photo]; 145 | 146 | if (sender.tag==102) { 147 | NSString *path_photo1l=[[NSBundle mainBundle] pathForResource:@"photo1l" ofType:@"jpg"]; 148 | 149 | photo=[[ZYQPhoto alloc] initWithFilePath:path_photo1l]; 150 | photo.caption=@"Grotto of the Madonna"; 151 | [photos addObject:photo]; 152 | 153 | } 154 | 155 | ZYQPhotoBrowser *browser=[[ZYQPhotoBrowser alloc] initWithPhotos:photos animatedFromView:sender]; 156 | browser.delegate = self; 157 | browser.displayActionButton = false; 158 | browser.displayArrowButton = NO; 159 | browser.displayDoneButton=NO; 160 | browser.autoHideInterface=NO; 161 | browser.displayCounterLabel = true; 162 | browser.useZoomAnimation = true; 163 | browser.scaleImage = sender.currentImage; 164 | browser.dismissOnTouch = true; 165 | browser.displayTitle= YES; 166 | 167 | [self presentViewController:browser animated:YES completion:nil]; 168 | 169 | } 170 | 171 | -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{ 172 | return 3; 173 | } 174 | 175 | -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ 176 | switch (section) { 177 | case 0: 178 | return 1; 179 | case 1: 180 | return 3; 181 | case 2: 182 | return 0; 183 | default: 184 | return 0; 185 | } 186 | } 187 | 188 | -(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{ 189 | switch (section) { 190 | case 0: 191 | return @"Single photo"; 192 | case 1: 193 | return @"Multiple photos"; 194 | case 2: 195 | return @"Photos on screen"; 196 | default: 197 | return @""; 198 | } 199 | } 200 | 201 | -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ 202 | static NSString *cellIdentifier=@"cell"; 203 | UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:cellIdentifier]; 204 | if (cell==nil) { 205 | cell=[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; 206 | [cell setSelectionStyle:UITableViewCellSelectionStyleNone]; 207 | } 208 | 209 | if (indexPath.section==0) { 210 | cell.textLabel.text=@"Local photo"; 211 | } 212 | else if (indexPath.section==1){ 213 | switch (indexPath.row) { 214 | case 0: 215 | cell.textLabel.text=@"Local photos"; 216 | break; 217 | case 1: 218 | cell.textLabel.text=@"Photos from Baidu"; 219 | break; 220 | case 2: 221 | cell.textLabel.text=@"Photos from Baidu - Custom"; 222 | break; 223 | 224 | default: 225 | break; 226 | } 227 | } 228 | return cell; 229 | } 230 | 231 | -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ 232 | NSMutableArray *photos=[[NSMutableArray alloc] init]; 233 | 234 | ZYQPhoto *photo=nil; 235 | 236 | if (indexPath.section==0) { 237 | NSString *path_photo2l=[[NSBundle mainBundle] pathForResource:@"photo2l" ofType:@"jpg"]; 238 | 239 | photo=[[ZYQPhoto alloc] initWithFilePath:path_photo2l]; 240 | photo.caption=@"The London Eye is a giant Ferris wheel situated on the banks of the River Thames, in London, England."; 241 | [photos addObject:photo]; 242 | } 243 | else if(indexPath.section==1){ 244 | if (indexPath.row==0) { 245 | NSString *path_photo1l=[[NSBundle mainBundle] pathForResource:@"photo1l" ofType:@"jpg"]; 246 | 247 | photo=[[ZYQPhoto alloc] initWithFilePath:path_photo1l]; 248 | photo.caption=@"Grotto of the Madonna"; 249 | [photos addObject:photo]; 250 | 251 | NSString *path_photo2l=[[NSBundle mainBundle] pathForResource:@"photo2l" ofType:@"jpg"]; 252 | 253 | photo=[[ZYQPhoto alloc] initWithFilePath:path_photo2l]; 254 | photo.caption=@"The London Eye is a giant Ferris wheel situated on the banks of the River Thames, in London, England."; 255 | [photos addObject:photo]; 256 | 257 | NSString *path_photo3l=[[NSBundle mainBundle] pathForResource:@"photo3l" ofType:@"jpg"]; 258 | 259 | photo=[[ZYQPhoto alloc] initWithFilePath:path_photo3l]; 260 | photo.caption=@"York Floods"; 261 | [photos addObject:photo]; 262 | 263 | NSString *path_photo4l=[[NSBundle mainBundle] pathForResource:@"photo4l" ofType:@"jpg"]; 264 | 265 | photo=[[ZYQPhoto alloc] initWithFilePath:path_photo4l]; 266 | photo.caption=@"Campervan"; 267 | [photos addObject:photo]; 268 | 269 | NSString *path_photo5l=[[NSBundle mainBundle] pathForResource:@"photo5l" ofType:@"gif"]; 270 | 271 | photo=[[ZYQPhoto alloc] initWithFilePath:path_photo5l]; 272 | photo.caption=@"Gif"; 273 | [photos addObject:photo]; 274 | 275 | } 276 | else if(indexPath.row==1||indexPath.row==2){ 277 | NSArray *photosWithURLArray=@[ 278 | [NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1498113175410&di=bd10bcd2b2a45906259f4e823bbf9df6&imgtype=0&src=http%3A%2F%2Fpic.58pic.com%2F58pic%2F14%2F27%2F45%2F71r58PICmDM_1024.jpg"] 279 | ,[NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1498113236506&di=004225eba24d74087feaeaa9484db047&imgtype=0&src=http%3A%2F%2Fic.topit.me%2Fc%2F3c%2Ff6%2F1110230348da8f63cco.jpg"] 280 | ,[NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b10000_10000&sec=1498103182&di=eb660bada9ced24acbc9153464aaedcd&src=http://www.bz55.com/uploads/allimg/141120/139-141120151946.jpg"] 281 | ,[NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1498139264538&di=6e5cedbfecc6a0e4bce2fc0005f56785&imgtype=0&src=http%3A%2F%2Fimg3.a0bi.com%2Fupload%2Fttq%2F20140719%2F1405761279700.gif"] 282 | ]; 283 | 284 | NSArray *photosWithURL=[ZYQPhoto photosWithURLs:photosWithURLArray]; 285 | photos=[NSMutableArray arrayWithArray:photosWithURL]; 286 | 287 | } 288 | } 289 | 290 | ZYQPhotoBrowser *browser=[[ZYQPhotoBrowser alloc] initWithPhotos:photos]; 291 | browser.delegate=self; 292 | if (indexPath.section==1) { 293 | if (indexPath.row==1) { 294 | browser.displayCounterLabel = true; 295 | browser.displayActionButton = false; 296 | } 297 | else if (indexPath.row==2){ 298 | browser.actionButtonTitles = @[@"Option 1", @"Option 2", @"Option 3", @"Option 4"]; 299 | browser.displayCounterLabel = true; 300 | browser.useWhiteBackgroundColor = true; 301 | browser.leftArrowImage = [UIImage imageNamed:@"PhotoBrowser_customArrowLeft"]; 302 | browser.rightArrowImage = [UIImage imageNamed:@"PhotoBrowser_customArrowRight"]; 303 | browser.leftArrowSelectedImage = [UIImage imageNamed:@"PhotoBrowser_customArrowLeftSelected"]; 304 | browser.rightArrowSelectedImage = [UIImage imageNamed:@"PhotoBrowser_customArrowRightSelected"]; 305 | browser.doneButtonImage = [UIImage imageNamed:@"PhotoBrowser_customDoneButton"]; 306 | browser.view.tintColor = [UIColor orangeColor]; 307 | browser.progressTintColor = [UIColor orangeColor]; 308 | browser.trackTintColor = [UIColor colorWithWhite:0.8 alpha:1]; 309 | } 310 | else{ 311 | browser.displayToolbar=NO; 312 | } 313 | } 314 | #ifdef CUSTOM 315 | browser.customBackgroud=[[BackgroudBlurView alloc] init]; 316 | browser.gifSupportImageViewClass=[YYAnimatedImageView class]; 317 | #else 318 | #endif 319 | [self presentViewController:browser animated:YES completion:nil]; 320 | 321 | } 322 | 323 | -(void)photoBrowser:(ZYQPhotoBrowser *)photoBrowser didShowPhotoAtIndex:(NSUInteger)index{ 324 | ZYQPhoto *photo=[photoBrowser photoAtIndex:index]; 325 | #ifdef CUSTOM 326 | BackgroudBlurView *blurView=(BackgroudBlurView*)photoBrowser.customBackgroud; 327 | dispatch_async(dispatch_get_main_queue(), ^{ 328 | if (photo.underlyingImage) { 329 | blurView.imgView.image=photo.underlyingImage; 330 | } 331 | else{ 332 | [blurView.imgView yy_setImageWithURL:photo.photoURL options:YYWebImageOptionAllowBackgroundTask]; 333 | } 334 | }); 335 | #else 336 | #endif 337 | 338 | NSLog(@"Did show photoBrowser with photo index: %zi, photo caption: %@",index,photo.caption); 339 | } 340 | 341 | -(void)photoBrowser:(ZYQPhotoBrowser *)photoBrowser willDismissAtPageIndex:(NSUInteger)index{ 342 | ZYQPhoto *photo=[photoBrowser photoAtIndex:index]; 343 | NSLog(@"Did show photoBrowser with photo index: %zi, photo caption: %@",index,photo.caption); 344 | } 345 | 346 | -(void)photoBrowser:(ZYQPhotoBrowser *)photoBrowser didDismissAtPageIndex:(NSUInteger)index{ 347 | ZYQPhoto *photo=[photoBrowser photoAtIndex:index]; 348 | NSLog(@"Did show photoBrowser with photo index: %zi, photo caption: %@",index,photo.caption); 349 | } 350 | 351 | -(void)photoBrowser:(ZYQPhotoBrowser *)photoBrowser didDismissActionSheetWithButtonIndex:(NSUInteger)buttonIndex photoIndex:(NSUInteger)photoIndex{ 352 | ZYQPhoto *photo=[photoBrowser photoAtIndex:buttonIndex]; 353 | NSLog(@"Did show photoBrowser with photo index: %zi, photo caption: %@",buttonIndex,photo.caption); 354 | 355 | } 356 | 357 | @end 358 | -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo/ZYQPhotoBrowser+Custom.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZYQPhotoBrowser+Custom.h 3 | // PhotoBrowserDemo 4 | // 5 | // Created by admin on 2017/6/22. 6 | // Copyright © 2017年 admin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ZYQPhotoBrowser (Custom) 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo/ZYQPhotoBrowser+Custom.m: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // ZYQPhotoBrowser+Custom.m 4 | // PhotoBrowserDemo 5 | // 6 | // Created by admin on 2017/6/22. 7 | // Copyright © 2017年 admin. All rights reserved. 8 | // 9 | 10 | #import "ZYQPhotoBrowser+Custom.h" 11 | #import "ZYQPhoto.h" 12 | #import "ZYQZoomingScrollView.h" 13 | 14 | #import "DACircularProgressView.h" 15 | #import "YYWebImage.h" 16 | 17 | @implementation ZYQPhotoBrowser (Custom) 18 | 19 | @end 20 | 21 | @interface ZYQPhoto (Custom) 22 | 23 | @end 24 | 25 | @implementation ZYQPhoto (Custom) 26 | 27 | -(void)loadImageWithURL:(NSURL*)url updateProgressBlock:(void (^)(CGFloat progress))updateProgressBlock completedBlock:(void (^)(UIImage *image,NSError *error))completedBlock{ 28 | [[YYWebImageManager sharedManager] requestImageWithURL:url options:YYWebImageOptionAllowBackgroundTask progress:^(NSInteger receivedSize, NSInteger expectedSize) { 29 | if (updateProgressBlock) { 30 | updateProgressBlock(receivedSize/expectedSize*1.0); 31 | } 32 | } transform:nil completion:^(UIImage * _Nullable image, NSURL * _Nonnull url, YYWebImageFromType from, YYWebImageStage stage, NSError * _Nullable error) { 33 | if (completedBlock) { 34 | completedBlock(image,error); 35 | } 36 | }]; 37 | } 38 | 39 | -(id)loadImageWithFile:(NSString *)path{ 40 | return [YYImage imageWithContentsOfFile:path]; 41 | } 42 | 43 | @end 44 | 45 | @interface ZYQZoomingScrollView (Custom) 46 | 47 | @end 48 | 49 | @implementation ZYQZoomingScrollView (Custom) 50 | 51 | -(id)getCustomProgressViewWithBrowser:(ZYQPhotoBrowser *)browser{ 52 | CGRect screenBound = [[UIScreen mainScreen] bounds]; 53 | CGFloat screenWidth = screenBound.size.width; 54 | CGFloat screenHeight = screenBound.size.height; 55 | 56 | DACircularProgressView *tmpProgressView = [[DACircularProgressView alloc] initWithFrame:CGRectMake((screenWidth-35.)/2., (screenHeight-35.)/2, 35.0f, 35.0f)]; 57 | [tmpProgressView setProgress:0.0f]; 58 | tmpProgressView.tag = 101; 59 | tmpProgressView.thicknessRatio = 0.1; 60 | tmpProgressView.roundedCorners = NO; 61 | tmpProgressView.trackTintColor = browser.trackTintColor ? browser.trackTintColor : [UIColor colorWithWhite:0.2 alpha:1]; 62 | tmpProgressView.progressTintColor = browser.progressTintColor ? browser.progressTintColor : [UIColor colorWithWhite:1.0 alpha:1]; 63 | 64 | _progressView=tmpProgressView; 65 | 66 | return tmpProgressView; 67 | } 68 | 69 | @end 70 | 71 | @interface DACircularProgressView (Custom) 72 | 73 | @end 74 | 75 | @implementation DACircularProgressView (Custom) 76 | 77 | -(void)setZyq_progress:(CGFloat)zyq_progress{ 78 | [self setProgress:zyq_progress animated:YES]; 79 | } 80 | 81 | -(CGFloat)zyq_progress{ 82 | return [self progress]; 83 | } 84 | 85 | @end 86 | -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // PhotoBrowserDemo 4 | // 5 | // Created by admin on 2017/6/21. 6 | // Copyright © 2017年 admin. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemoTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemoTests/PhotoBrowserDemoTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // PhotoBrowserDemoTests.m 3 | // PhotoBrowserDemoTests 4 | // 5 | // Created by admin on 2017/6/21. 6 | // Copyright © 2017年 admin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface PhotoBrowserDemoTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation PhotoBrowserDemoTests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | } 21 | 22 | - (void)tearDown { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | [super tearDown]; 25 | } 26 | 27 | - (void)testExample { 28 | // This is an example of a functional test case. 29 | // Use XCTAssert and related functions to verify your tests produce the correct results. 30 | } 31 | 32 | - (void)testPerformanceExample { 33 | // This is an example of a performance test case. 34 | [self measureBlock:^{ 35 | // Put the code you want to measure the time of here. 36 | }]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemoUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /PhotoBrowserDemo/PhotoBrowserDemoUITests/PhotoBrowserDemoUITests.m: -------------------------------------------------------------------------------- 1 | // 2 | // PhotoBrowserDemoUITests.m 3 | // PhotoBrowserDemoUITests 4 | // 5 | // Created by admin on 2017/6/21. 6 | // Copyright © 2017年 admin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface PhotoBrowserDemoUITests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation PhotoBrowserDemoUITests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | 22 | // In UI tests it is usually best to stop immediately when a failure occurs. 23 | self.continueAfterFailure = NO; 24 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 25 | [[[XCUIApplication alloc] init] launch]; 26 | 27 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 28 | } 29 | 30 | - (void)tearDown { 31 | // Put teardown code here. This method is called after the invocation of each test method in the class. 32 | [super tearDown]; 33 | } 34 | 35 | - (void)testExample { 36 | // Use recording to get started writing UI tests. 37 | // Use XCTAssert and related functions to verify your tests produce the correct results. 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /PhotoBrowserDemo/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | target 'PhotoBrowserDemo' do 5 | # Uncomment the next line if you're using Swift or would like to use dynamic frameworks 6 | # use_frameworks! 7 | 8 | # Pods for PhotoBrowserDemo 9 | #Default core 10 | pod 'ZYQPhotoBrowser', :path => '../ZYQPhotoBrowser.podspec' 11 | 12 | 13 | target 'PhotoBrowserDemoTests' do 14 | inherit! :search_paths 15 | # Pods for testing 16 | end 17 | 18 | target 'PhotoBrowserDemoUITests' do 19 | inherit! :search_paths 20 | # Pods for testing 21 | end 22 | 23 | end 24 | 25 | target 'PhotoBrowserCustomCoreDemo' do 26 | 27 | #NoDependency custom core 28 | pod 'ZYQPhotoBrowser-NoDependency', :path => '../ZYQPhotoBrowser-NoDependency.podspec' 29 | pod 'YYWebImage' 30 | pod 'YYImage/WebP' 31 | pod 'DACircularProgress' 32 | end 33 | -------------------------------------------------------------------------------- /PhotoBrowserDemo/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - DACircularProgress (2.3.1) 3 | - FLAnimatedImage (1.0.12) 4 | - SDWebImage (4.0.0): 5 | - SDWebImage/Core (= 4.0.0) 6 | - SDWebImage/Core (4.0.0) 7 | - SDWebImage/GIF (4.0.0): 8 | - FLAnimatedImage (~> 1.0) 9 | - SDWebImage/Core 10 | - YYCache (1.0.4) 11 | - YYImage (1.0.4): 12 | - YYImage/Core (= 1.0.4) 13 | - YYImage/Core (1.0.4) 14 | - YYImage/WebP (1.0.4): 15 | - YYImage/Core 16 | - YYWebImage (1.0.5): 17 | - YYCache 18 | - YYImage 19 | - ZYQPhotoBrowser (1.0): 20 | - DACircularProgress 21 | - SDWebImage (= 4.0.0) 22 | - SDWebImage/GIF (= 4.0.0) 23 | - ZYQPhotoBrowser-NoDependency (1.0) 24 | 25 | DEPENDENCIES: 26 | - DACircularProgress 27 | - YYImage/WebP 28 | - YYWebImage 29 | - ZYQPhotoBrowser (from `../ZYQPhotoBrowser.podspec`) 30 | - ZYQPhotoBrowser-NoDependency (from `../ZYQPhotoBrowser-NoDependency.podspec`) 31 | 32 | EXTERNAL SOURCES: 33 | ZYQPhotoBrowser: 34 | :path: "../ZYQPhotoBrowser.podspec" 35 | ZYQPhotoBrowser-NoDependency: 36 | :path: "../ZYQPhotoBrowser-NoDependency.podspec" 37 | 38 | SPEC CHECKSUMS: 39 | DACircularProgress: 4dd437c0fc3da5161cb289e07ac449493d41db71 40 | FLAnimatedImage: 4a0b56255d9b05f18b6dd7ee06871be5d3b89e31 41 | SDWebImage: 76a6348bdc74eb5a55dd08a091ef298e56b55e41 42 | YYCache: 8105b6638f5e849296c71f331ff83891a4942952 43 | YYImage: 1e1b62a9997399593e4b9c4ecfbbabbf1d3f3b54 44 | YYWebImage: 5f7f36aee2ae293f016d418c7d6ba05c4863e928 45 | ZYQPhotoBrowser: 1b74c28ddc38991e4f1f7e6cabe95608246493f6 46 | ZYQPhotoBrowser-NoDependency: 94c7e339ec93077c3db2f3347ba82088951de3d7 47 | 48 | PODFILE CHECKSUM: 8d9697984782e14f8854bba8eb67f88493814932 49 | 50 | COCOAPODS: 1.2.1 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ZYQPhotoBrowser ![](http://cocoapod-badges.herokuapp.com/v/ZYQPhotoBrowser/badge.png) ![](http://cocoapod-badges.herokuapp.com/p/ZYQPhotoBrowser/badge.png) 2 | 3 | ZYQPhotoBrowser is a new implementation based on [MWPhotoBrowser](https://github.com/mwaterfall/MWPhotoBrowser). 4 | 5 | We've added both user experience and technical features inspired by Facebook's and Tweetbot's photo browsers. 6 | 7 | ## Screenshots 8 | 9 | ![Screenshot1](http://heroims.github.io/ZYQPhotoBrowser/Untitled1.gif "Screenshot1") 10 | 11 | ![Screenshot2](http://heroims.github.io/ZYQPhotoBrowser/Untitled2.gif "Screenshot1") 12 | 13 | ![Screenshot2](http://heroims.github.io/ZYQPhotoBrowser/Untitled3.gif "Screenshot1") 14 | 15 | ## Usage 16 | 17 | See the code snippet below for an example of how to implement the photo browser. 18 | 19 | First create a photos array containing ZYQPhoto objects: 20 | 21 | ``` objective-c 22 | // URLs array 23 | NSArray *photosURL = @[ 24 | [NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1498113175410&di=bd10bcd2b2a45906259f4e823bbf9df6&imgtype=0&src=http%3A%2F%2Fpic.58pic.com%2F58pic%2F14%2F27%2F45%2F71r58PICmDM_1024.jpg"] 25 | ,[NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1498113236506&di=004225eba24d74087feaeaa9484db047&imgtype=0&src=http%3A%2F%2Fic.topit.me%2Fc%2F3c%2Ff6%2F1110230348da8f63cco.jpg"] 26 | ,[NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b10000_10000&sec=1498103182&di=eb660bada9ced24acbc9153464aaedcd&src=http://www.bz55.com/uploads/allimg/141120/139-141120151946.jpg"] 27 | ,[NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1498139264538&di=6e5cedbfecc6a0e4bce2fc0005f56785&imgtype=0&src=http%3A%2F%2Fimg3.a0bi.com%2Fupload%2Fttq%2F20140719%2F1405761279700.gif"] 28 | ]; 29 | 30 | // Create an array to store ZYQPhoto objects 31 | NSMutableArray *photos = [NSMutableArray new]; 32 | 33 | for (NSURL *url in photosURL) { 34 | ZYQPhoto *photo = [ZYQPhoto photoWithURL:url]; 35 | [photos addObject:photo]; 36 | } 37 | 38 | // Or use this constructor to receive an NSArray of ZYQPhoto objects from your NSURL objects 39 | NSArray *photos = [ZYQPhoto photosWithURLs:photosURL]; 40 | ```` 41 | 42 | There are two main ways to presente the photoBrowser, with a fade on screen or with a zooming effect from an existing view. 43 | 44 | Using a simple fade transition: 45 | 46 | ``` objective-c 47 | ZYQPhotoBrowser *browser = [[ZYQPhotoBrowser alloc] initWithPhotos:photos]; 48 | ``` 49 | 50 | Zooming effect from a view: 51 | 52 | ``` objective-c 53 | ZYQPhotoBrowser *browser = [[ZYQPhotoBrowser alloc] initWithPhotos:photos animatedFromView:sender]; 54 | ``` 55 | 56 | When using this animation you can set the `scaleImage` property, in case the image from the view is not the same as the one that will be shown on the browser, so it will dynamically scale it: 57 | 58 | ``` objective-c 59 | browser.scaleImage = buttonSender.currentImage; 60 | ``` 61 | 62 | Presenting using a modal view controller: 63 | 64 | ``` objective-c 65 | [self presentViewController:browser animated:YES completion:nil]; 66 | ``` 67 | 68 | ### Customization 69 | 70 | ##### Toolbar 71 | 72 | You can customize the toolbar. There are three boolean properties you can set: displayActionButton (default is YES), displayArrowButton (default is YES) and displayCounterLabel (default is NO). If you dont want the toolbar at all, you can set displayToolbar = NO. 73 | 74 | Toolbar setup example: 75 | ``` objective-c 76 | browser.displayActionButton = NO; 77 | browser.displayArrowButton = YES; 78 | browser.displayCounterLabel = YES; 79 | ``` 80 | 81 | It is possible to use your own image on the toolbar arrows: 82 | ``` objective-c 83 | browser.leftArrowImage = [UIImage imageNamed:@"PhotoBrowser_customArrowLeft.png"]; 84 | browser.rightArrowImage = [UIImage imageNamed:@"PhotoBrowser_customArrowRight.png"]; 85 | browser.leftArrowSelectedImage = [UIImage imageNamed:@"PhotoBrowser_customArrowLeftSelected.png"]; 86 | browser.rightArrowSelectedImage = [UIImage imageNamed:@"PhotoBrowser_customArrowRightSelected.png"]; 87 | ``` 88 | 89 | If you want to use custom actions, set the actionButtonTitles array with the titles for the actionSheet. Then, implement the photoBrowser:didDismissActionSheetWithButtonIndex:photoIndex: method, from the ZYQPhotoBrowser delegate 90 | 91 | ``` objective-c 92 | browser.actionButtonTitles = @[@"Option 1", @"Option 2", @"Option 3", @"Option 4"]; 93 | ``` 94 | 95 | #### Others 96 | 97 | Others customizations you can make are: use white background color, don't display the done button and change the done button background image: 98 | ``` objective-c 99 | browser.useWhiteBackgroundColor = YES; 100 | browser.displayDoneButton = NO; 101 | browser.doneButtonImage = [UIImage imageNamed:@"PhotoBrowser_customDoneButton.png"]; 102 | ``` 103 | 104 | If you want to keep the interface shown when the user is scrolling : 105 | ``` objective-c 106 | browser.autoHideInterface = NO; 107 | ``` 108 | 109 | You can use a smooth zoom animation when presenting and dismissing a photo: 110 | ``` objective-c 111 | browser.useZoomAnimation = YES; 112 | ``` 113 | 114 | If the presenting view controller doesn't have a status bar, in some cases you can force it to be hidden: 115 | ``` objective-c 116 | browser.forceHideStatusBar = YES; 117 | ``` 118 | 119 | It's possible to disable the vertical dismiss swipe gesture: 120 | ``` objective-c 121 | browser.disableVerticalSwipe = YES; 122 | ``` 123 | 124 | Dismiss the photo browser with a touch (instead of showing/hiding controls): 125 | ``` objective-c 126 | browser.dismissOnTouch = YES; 127 | ``` 128 | You can use custom backgroud 129 | ``` objective-c 130 | browser.customBackgroud=[[BackgroudBlurView alloc] init]; 131 | ``` 132 | 133 | ### Photo Captions 134 | 135 | Photo captions can be displayed simply by setting the `caption` property on specific photos: 136 | ``` objective-c 137 | ZYQPhoto *photo = [ZYQPhoto photoWithFilePath:[[NSBundle mainBundle] pathForResource:@"photo2l" ofType:@"jpg"]]; 138 | photo.caption = @"Campervan"; 139 | ``` 140 | 141 | No caption will be displayed if the caption property is not set. 142 | 143 | #### Custom Captions 144 | 145 | 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: 146 | 147 | 1. Optionally use a subclass of `ZYQPhoto` for your photos so you can store more data than a simple caption string. 148 | 2. Subclass `ZYQCaptionView` 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 `ZYQCaptionView.h` 149 | 3. Implement the `-photoBrowser:captionViewForPhotoAtIndex:` ZYQPhotoBrowser delegate method (shown below). 150 | 151 | Example delegate method for custom caption view: 152 | ``` objective-c 153 | - (ZYQCaptionView *)photoBrowser:(ZYQPhotoBrowser *)photoBrowser captionViewForPhotoAtIndex:(NSUInteger)index { 154 | ZYQPhoto *photo = [self.photos objectAtIndex:index]; 155 | MyCaptionViewSubclass *captionView = [[MyCaptionViewSubclass alloc] initWithPhoto:photo]; 156 | return captionView; 157 | } 158 | ``` 159 | #### Custom Opensource libraries 160 | You need to implement protocol 161 | ``` objective-c 162 | //ZYQPhotoBrowser's 163 | @protocol ZYQPhotoBrowserCustomProtocol 164 | 165 | @required 166 | - (void)custom_animateView:(UIView *)view toFrame:(CGRect)frame completion:(void (^)(void))completion; 167 | 168 | @end 169 | 170 | //ZYQPhoto's 171 | @protocol ZYQPhotoCustomLoadProtocol 172 | 173 | @required 174 | -(void)loadImageWithURL:(NSURL*)url updateProgressBlock:(void (^)(CGFloat progress))updateProgressBlock completedBlock:(void (^)(UIImage *image,NSError *error))completedBlock; 175 | -(id)loadImageWithFile:(NSString *)path; 176 | 177 | @end 178 | 179 | //ZYQZoomingScrollView's 180 | @class ZYQPhotoBrowser; 181 | @protocol ZYQZoomingScrollViewCustomProtocol 182 | 183 | @required 184 | -(id)getCustomProgressViewWithBrowser:(ZYQPhotoBrowser*)browser; 185 | 186 | @end 187 | 188 | //Your ProgressView's 189 | @protocol ZYQProgressViewProtocol 190 | 191 | @required 192 | @property(nonatomic,assign)CGFloat zyq_progress; 193 | 194 | @end 195 | 196 | ``` 197 | Example 198 | 199 | add `pod 'YYWebImage'` to your Podfile. 200 | 201 | ``` objective-c 202 | 203 | @interface ZYQPhoto (Custom) 204 | 205 | @end 206 | 207 | @implementation ZYQPhoto (Custom) 208 | 209 | -(void)loadImageWithURL:(NSURL*)url updateProgressBlock:(void (^)(CGFloat progress))updateProgressBlock completedBlock:(void (^)(UIImage *image,NSError *error))completedBlock{ 210 | [[YYWebImageManager sharedManager] requestImageWithURL:url options:YYWebImageOptionAllowBackgroundTask progress:^(NSInteger receivedSize, NSInteger expectedSize) { 211 | if (updateProgressBlock) { 212 | updateProgressBlock(receivedSize/expectedSize*1.0); 213 | } 214 | } transform:nil completion:^(UIImage * _Nullable image, NSURL * _Nonnull url, YYWebImageFromType from, YYWebImageStage stage, NSError * _Nullable error) { 215 | if (completedBlock) { 216 | completedBlock(image,error); 217 | } 218 | }]; 219 | } 220 | 221 | -(id)loadImageWithFile:(NSString *)path{ 222 | return [YYImage imageWithContentsOfFile:path]; 223 | } 224 | 225 | @end 226 | 227 | @interface ZYQZoomingScrollView (Custom) 228 | 229 | @end 230 | 231 | @implementation ZYQZoomingScrollView (Custom) 232 | 233 | -(id)getCustomProgressViewWithBrowser:(ZYQPhotoBrowser *)browser{ 234 | CGRect screenBound = [[UIScreen mainScreen] bounds]; 235 | CGFloat screenWidth = screenBound.size.width; 236 | CGFloat screenHeight = screenBound.size.height; 237 | 238 | DACircularProgressView *tmpProgressView = [[DACircularProgressView alloc] initWithFrame:CGRectMake((screenWidth-35.)/2., (screenHeight-35.)/2, 35.0f, 35.0f)]; 239 | [tmpProgressView setProgress:0.0f]; 240 | tmpProgressView.tag = 101; 241 | tmpProgressView.thicknessRatio = 0.1; 242 | tmpProgressView.roundedCorners = NO; 243 | tmpProgressView.trackTintColor = browser.trackTintColor ? browser.trackTintColor : [UIColor colorWithWhite:0.2 alpha:1]; 244 | tmpProgressView.progressTintColor = browser.progressTintColor ? browser.progressTintColor : [UIColor colorWithWhite:1.0 alpha:1]; 245 | 246 | _progressView=tmpProgressView; 247 | 248 | return tmpProgressView; 249 | } 250 | 251 | @end 252 | 253 | @interface DACircularProgressView (Custom) 254 | 255 | @end 256 | 257 | @implementation DACircularProgressView (Custom) 258 | 259 | -(void)setZyq_progress:(CGFloat)zyq_progress{ 260 | [self setProgress:zyq_progress animated:YES]; 261 | } 262 | 263 | -(CGFloat)zyq_progress{ 264 | return [self progress]; 265 | } 266 | 267 | @end 268 | ``` 269 | 270 | 271 | ## Adding to your project 272 | 273 | ### Using CocoaPods 274 | 275 | Just add `pod 'ZYQPhotoBrowser'` to your Podfile. 276 | 277 | If you want to use custom libraries 278 | 279 | Just add `pod 'ZYQPhotoBrowser-NoDependency'` to your Podfile. 280 | 281 | ### Including Source Directly Into Your Project 282 | 283 | #### ZYQPhotoBrowser Opensource libraries used 284 | 285 | - [SDWebImage](https://github.com/rs/SDWebImage) 286 | - [DACircularProgress](https://github.com/danielamitay/DACircularProgress) 287 | 288 | ## Licence 289 | 290 | This project uses MIT License. 291 | -------------------------------------------------------------------------------- /ZYQPhotoBrowser-NoDependency.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "ZYQPhotoBrowser-NoDependency" 3 | s.summary = "Photo Browser / Viewer inspired by Facebook's and Tweetbot's with ARC support, swipe-to-dismiss, image progress and more." 4 | s.version = "1.2" 5 | s.homepage = "https://github.com/heroims/ZYQPhotoBrowser" 6 | s.license = { :type => 'MIT', :file => 'README.md' } 7 | s.author = { "Eduardo Callado" => "eduardo_tasker@hotmail.com" } 8 | s.source = { :git => "https://github.com/heroims/ZYQPhotoBrowser.git", :tag => "#{s.version}" } 9 | s.platform = :ios, '6.0' 10 | s.requires_arc = true 11 | s.framework = 'MessageUI', 'QuartzCore', 'SystemConfiguration', 'MobileCoreServices', 'Security' 12 | s.source_files = 'ZYQPhotoBrowser/*.{h,m}' 13 | s.resources = 'ZYQPhotoBrowser/ZYQPhotoBrowser.bundle', 'ZYQPhotoBrowser/ZYQPBLocalizations.bundle' 14 | s.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => 'NODEPENDENCY=11111'} 15 | end 16 | -------------------------------------------------------------------------------- /ZYQPhotoBrowser.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "ZYQPhotoBrowser" 3 | s.summary = "Photo Browser / Viewer inspired by Facebook's and Tweetbot's with ARC support, swipe-to-dismiss, image progress and more." 4 | s.version = "1.2" 5 | s.homepage = "https://github.com/heroims/ZYQPhotoBrowser" 6 | s.license = { :type => 'MIT', :file => 'README.md' } 7 | s.author = { "Eduardo Callado" => "eduardo_tasker@hotmail.com" } 8 | s.source = { :git => "https://github.com/heroims/ZYQPhotoBrowser.git", :tag => "#{s.version}" } 9 | s.platform = :ios, '6.0' 10 | s.requires_arc = true 11 | s.framework = 'MessageUI', 'QuartzCore', 'SystemConfiguration', 'MobileCoreServices', 'Security' 12 | s.source_files = 'ZYQPhotoBrowser/*.{h,m}' 13 | s.resources = 'ZYQPhotoBrowser/ZYQPhotoBrowser.bundle', 'ZYQPhotoBrowser/ZYQPBLocalizations.bundle' 14 | s.dependency 'SDWebImage', '4.0.0' 15 | s.dependency 'SDWebImage/GIF', '4.0.0' 16 | 17 | s.dependency 'DACircularProgress' 18 | end 19 | -------------------------------------------------------------------------------- /ZYQPhotoBrowser/ZYQCaptionView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZYQCaptionView.h 3 | // ZYQPhotoBrowser 4 | // 5 | // Created by Michael Waterfall on 30/12/2011. 6 | // Copyright (c) 2011 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "ZYQPhotoProtocol.h" 11 | 12 | @interface ZYQCaptionView : UIView 13 | 14 | @property (nonatomic, strong) UILabel *label; 15 | @property (nonatomic, strong, readonly) id photo; 16 | 17 | // Init 18 | - (id)initWithPhoto:(id)photo; 19 | 20 | // To create your own custom caption view, subclass this view 21 | // and override the following two methods (as well as any other 22 | // UIView methods that you see fit): 23 | 24 | // Override -setupCaption so setup your subviews and customise the appearance 25 | // of your custom caption 26 | // You can access the photo's data by accessing the _photo ivar 27 | // If you need more data per photo then simply subclass ZYQPhoto and return your 28 | // subclass to the photo browsers -photoBrowser:photoAtIndex: delegate method 29 | - (void)setupCaption; 30 | 31 | // Override -sizeThatFits: and return a CGSize specifying the height of your 32 | // custom caption view. With width property is ignored and the caption is displayed 33 | // the full width of the screen 34 | - (CGSize)sizeThatFits:(CGSize)size; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /ZYQPhotoBrowser/ZYQCaptionView.m: -------------------------------------------------------------------------------- 1 | // 2 | // ZYQCaptionView.m 3 | // ZYQPhotoBrowser 4 | // 5 | // Created by Michael Waterfall on 30/12/2011. 6 | // Copyright (c) 2011 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import "ZYQCaptionView.h" 10 | #import "ZYQPhoto.h" 11 | #import 12 | 13 | static const CGFloat labelPadding = 10; 14 | 15 | // Private 16 | @interface ZYQCaptionView () 17 | 18 | @property (nonatomic, strong, readwrite) id photo; 19 | 20 | @end 21 | 22 | @implementation ZYQCaptionView 23 | 24 | @synthesize label = _label; 25 | @synthesize photo = _photo; 26 | 27 | - (id)initWithPhoto:(id)photo { 28 | CGRect screenBound = [[UIScreen mainScreen] bounds]; 29 | CGFloat screenWidth = screenBound.size.width; 30 | 31 | if ([[UIDevice currentDevice] orientation] == UIDeviceOrientationLandscapeLeft || 32 | [[UIDevice currentDevice] orientation] == UIDeviceOrientationLandscapeRight) { 33 | screenWidth = screenBound.size.height; 34 | } 35 | 36 | self = [super initWithFrame:CGRectMake(0, 0, screenWidth, 44)]; // Random initial frame 37 | if (self) { 38 | _photo = photo; 39 | self.opaque = NO; 40 | 41 | [self setBackground]; 42 | 43 | [self setupCaption]; 44 | } 45 | 46 | return self; 47 | } 48 | 49 | - (CGSize)sizeThatFits:(CGSize)size { 50 | if (_label.text.length == 0) return CGSizeZero; 51 | 52 | CGFloat maxHeight = 9999; 53 | if (_label.numberOfLines > 0) maxHeight = _label.font.leading*_label.numberOfLines; 54 | 55 | /*CGSize textSizeOLD = [_label.text sizeWithFont:_label.font 56 | constrainedToSize:CGSizeMake(size.width - labelPadding*2, maxHeight) 57 | lineBreakMode:_label.lineBreakMode];*/ 58 | 59 | CGFloat width = size.width - labelPadding*2; 60 | 61 | CGFloat height = [_label sizeThatFits:CGSizeMake(width, CGFLOAT_MAX)].height; 62 | return CGSizeMake(size.width, height + labelPadding * 2); 63 | } 64 | 65 | - (void)setupCaption { 66 | _label = [[UILabel alloc] initWithFrame:CGRectMake(labelPadding, 0, 67 | self.bounds.size.width-labelPadding*2, 68 | self.bounds.size.height)]; 69 | _label.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; 70 | _label.opaque = NO; 71 | _label.backgroundColor = [UIColor clearColor]; 72 | _label.textAlignment = NSTextAlignmentCenter; 73 | _label.lineBreakMode = NSLineBreakByWordWrapping; 74 | _label.numberOfLines = 3; 75 | _label.textColor = [UIColor whiteColor]; 76 | _label.shadowColor = [UIColor colorWithWhite:0 alpha:0.5]; 77 | _label.shadowOffset = CGSizeMake(0, 1); 78 | _label.font = [UIFont systemFontOfSize:17]; 79 | if ([_photo respondsToSelector:@selector(caption)]) { 80 | _label.text = [_photo caption] ? [_photo caption] : @" "; 81 | } 82 | 83 | [self addSubview:_label]; 84 | } 85 | 86 | - (void)setBackground { 87 | UIView *fadeView = [[UIView alloc] initWithFrame:CGRectMake(0, -100, 10000, 130+100)]; // Static width, autoresizingMask is not working 88 | CAGradientLayer *gradient = [CAGradientLayer layer]; 89 | gradient.frame = fadeView.bounds; 90 | gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor colorWithWhite:0 alpha:0.0] CGColor], (id)[[UIColor colorWithWhite:0 alpha:0.8] CGColor], nil]; 91 | [fadeView.layer insertSublayer:gradient atIndex:0]; 92 | fadeView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; //UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleTopMargin|UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin; 93 | [self addSubview:fadeView]; 94 | } 95 | 96 | @end 97 | -------------------------------------------------------------------------------- /ZYQPhotoBrowser/ZYQPBConstants.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZYQPhotoBrowserConstants.h 3 | // PhotoBrowserDemo 4 | // 5 | // Created by Eduardo Callado on 10/7/13. 6 | // 7 | // 8 | 9 | #define PADDING 10 10 | #define PAGE_INDEX_TAG_OFFSET 1000 11 | #define PAGE_INDEX(page) ([(page) tag] - PAGE_INDEX_TAG_OFFSET) 12 | 13 | // Debug Logging 14 | #if 0 // Set to 1 to enable debug logging 15 | #define ZYQLog(x, ...) NSLog(x, ## __VA_ARGS__); 16 | #else 17 | #define ZYQLog(x, ...) 18 | #endif 19 | 20 | // 21 | // System Versioning Preprocessor Macros 22 | // 23 | 24 | //#define SYSTEM_VERSION_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame) 25 | //#define SYSTEM_VERSION_GREATER_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending) 26 | //#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending) 27 | //#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending) 28 | //#define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending) 29 | -------------------------------------------------------------------------------- /ZYQPhotoBrowser/ZYQPBLocalizations.bundle/de.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* No comment provided by engineer. */ 2 | "Cancel" = "Abbrechen"; 3 | 4 | /* Informing the user an item has finished copying */ 5 | "Copied" = "Kopiert"; 6 | 7 | /* No comment provided by engineer. */ 8 | "Copy" = "Kopieren"; 9 | 10 | /* Displayed with ellipsis as 'Copying...' when an item is in the process of being copied */ 11 | "Copying" = "Kopiere"; 12 | 13 | /* No comment provided by engineer. */ 14 | "Dismiss" = "Schließen"; 15 | 16 | /* No comment provided by engineer. */ 17 | "Done" = "Fertig"; 18 | 19 | /* No comment provided by engineer. */ 20 | "Email" = "Email"; 21 | 22 | /* No comment provided by engineer. */ 23 | "Email failed to send. Please try again." = "Senden fehlgeschlagen. Bitte erneut versuchen."; 24 | 25 | /* Informing the user a process has failed */ 26 | "Failed" = "Fehlgeschlagen"; 27 | 28 | /* Used in the context: 'Showing 1 of 3 items' */ 29 | "of" = "von"; 30 | 31 | /* No comment provided by engineer. */ 32 | "Photo" = "Foto"; 33 | 34 | /* Displayed with ellipsis as 'Preparing...' when an item is in the process of being prepared */ 35 | "Preparing" = "Vorbereiten"; 36 | 37 | /* No comment provided by engineer. */ 38 | "Save" = "Sichern"; 39 | 40 | /* Informing the user an item has been saved */ 41 | "Saved" = "Gesichert"; 42 | 43 | /* Displayed with ellipsis as 'Saving...' when an item is in the process of being saved */ 44 | "Saving" = "Sichere"; 45 | 46 | -------------------------------------------------------------------------------- /ZYQPhotoBrowser/ZYQPBLocalizations.bundle/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heroims/ZYQPhotoBrowser/0bfa3ce3a5cf5ce4a452bf1c5d5335a066a4a367/ZYQPhotoBrowser/ZYQPBLocalizations.bundle/en.lproj/Localizable.strings -------------------------------------------------------------------------------- /ZYQPhotoBrowser/ZYQPBLocalizations.bundle/es.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heroims/ZYQPhotoBrowser/0bfa3ce3a5cf5ce4a452bf1c5d5335a066a4a367/ZYQPhotoBrowser/ZYQPBLocalizations.bundle/es.lproj/Localizable.strings -------------------------------------------------------------------------------- /ZYQPhotoBrowser/ZYQPBLocalizations.bundle/fr.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heroims/ZYQPhotoBrowser/0bfa3ce3a5cf5ce4a452bf1c5d5335a066a4a367/ZYQPhotoBrowser/ZYQPBLocalizations.bundle/fr.lproj/Localizable.strings -------------------------------------------------------------------------------- /ZYQPhotoBrowser/ZYQPBLocalizations.bundle/ja.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* No comment provided by engineer. */ 2 | "Cancel" = "キャンセル"; 3 | 4 | /* Informing the user an item has finished copying */ 5 | "Copied" = "コピーされました"; 6 | 7 | /* No comment provided by engineer. */ 8 | "Copy" = "コピー"; 9 | 10 | /* Displayed with ellipsis as 'Copying...' when an item is in the process of being copied */ 11 | "Copying" = "コピー中"; 12 | 13 | /* No comment provided by engineer. */ 14 | "Dismiss" = "閉じる"; 15 | 16 | /* No comment provided by engineer. */ 17 | "Done" = "完了"; 18 | 19 | /* No comment provided by engineer. */ 20 | "Email" = "メール"; 21 | 22 | /* No comment provided by engineer. */ 23 | "Email failed to send. Please try again." = "メールの送信に失敗しました。もう一度送信してください。"; 24 | 25 | /* Informing the user a process has failed */ 26 | "Failed" = "失敗しました"; 27 | 28 | /* Used in the context: 'Showing 1 of 3 items' */ 29 | "of" = "/"; 30 | 31 | /* No comment provided by engineer. */ 32 | "Photo" = "写真"; 33 | 34 | /* Displayed with ellipsis as 'Preparing...' when an item is in the process of being prepared */ 35 | "Preparing" = "読み込み中"; 36 | 37 | /* No comment provided by engineer. */ 38 | "Save" = "保存"; 39 | 40 | /* Informing the user an item has been saved */ 41 | "Saved" = "保存されました"; 42 | 43 | /* Displayed with ellipsis as 'Saving...' when an item is in the process of being saved */ 44 | "Saving" = "保存中"; 45 | 46 | -------------------------------------------------------------------------------- /ZYQPhotoBrowser/ZYQPBLocalizations.bundle/nl.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* No comment provided by engineer. */ 2 | "Cancel" = "Annuleer"; 3 | 4 | /* Informing the user an item has finished copying */ 5 | "Copied" = "Gekopieerd"; 6 | 7 | /* No comment provided by engineer. */ 8 | "Copy" = "Kopieer"; 9 | 10 | /* Displayed with ellipsis as 'Copying...' when an item is in the process of being copied */ 11 | "Copying" = "Kopieer"; 12 | 13 | /* No comment provided by engineer. */ 14 | "Dismiss" = "Annuleer"; 15 | 16 | /* No comment provided by engineer. */ 17 | "Done" = "Klaae"; 18 | 19 | /* No comment provided by engineer. */ 20 | "Email" = "E-mail"; 21 | 22 | /* No comment provided by engineer. */ 23 | "Email failed to send. Please try again." = "Het versturen van de e-mail is mislukt. Probeer opnieuw."; 24 | 25 | /* Informing the user a process has failed */ 26 | "Failed" = "Mislukt"; 27 | 28 | /* Used in the context: 'Showing 1 of 3 items' */ 29 | "of" = "of"; 30 | 31 | /* No comment provided by engineer. */ 32 | "Photo" = "Foto"; 33 | 34 | /* Displayed with ellipsis as 'Preparing...' when an item is in the process of being prepared */ 35 | "Preparing" = "Voorbereiden"; 36 | 37 | /* No comment provided by engineer. */ 38 | "Save" = "Opslaan"; 39 | 40 | /* Informing the user an item has been saved */ 41 | "Saved" = "Opgeslagen"; 42 | 43 | /* Displayed with ellipsis as 'Saving...' when an item is in the process of being saved */ 44 | "Saving" = "Opslaan"; 45 | -------------------------------------------------------------------------------- /ZYQPhotoBrowser/ZYQPBLocalizations.bundle/pt.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* No comment provided by engineer. */ 2 | "Cancel" = "Cancelar"; 3 | 4 | /* Informing the user an item has finished copying */ 5 | "Copied" = "Copiado"; 6 | 7 | /* No comment provided by engineer. */ 8 | "Copy" = "Copiar"; 9 | 10 | /* Displayed with ellipsis as 'Copying...' when an item is in the process of being copied */ 11 | "Copying" = "Copiando"; 12 | 13 | /* No comment provided by engineer. */ 14 | "Dismiss" = "Fechar"; 15 | 16 | /* No comment provided by engineer. */ 17 | "Done" = "Fechar"; 18 | 19 | /* No comment provided by engineer. */ 20 | "Email" = "Email"; 21 | 22 | /* No comment provided by engineer. */ 23 | "Email failed to send. Please try again." = "Email falhou ao enviar. Por favor, tente novamente."; 24 | 25 | /* Informing the user a process has failed */ 26 | "Failed" = "Falhou"; 27 | 28 | /* Used in the context: 'Showing 1 of 3 items' */ 29 | "of" = "de"; 30 | 31 | /* No comment provided by engineer. */ 32 | "Photo" = "Foto"; 33 | 34 | /* Displayed with ellipsis as 'Preparing...' when an item is in the process of being prepared */ 35 | "Preparing" = "Preparando"; 36 | 37 | /* No comment provided by engineer. */ 38 | "Save" = "Salvar"; 39 | 40 | /* Informing the user an item has been saved */ 41 | "Saved" = "Salvada"; 42 | 43 | /* Displayed with ellipsis as 'Saving...' when an item is in the process of being saved */ 44 | "Saving" = "Salvando"; 45 | 46 | -------------------------------------------------------------------------------- /ZYQPhotoBrowser/ZYQPBLocalizations.bundle/ru.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heroims/ZYQPhotoBrowser/0bfa3ce3a5cf5ce4a452bf1c5d5335a066a4a367/ZYQPhotoBrowser/ZYQPBLocalizations.bundle/ru.lproj/Localizable.strings -------------------------------------------------------------------------------- /ZYQPhotoBrowser/ZYQPBLocalizations.bundle/zh-Hans.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heroims/ZYQPhotoBrowser/0bfa3ce3a5cf5ce4a452bf1c5d5335a066a4a367/ZYQPhotoBrowser/ZYQPBLocalizations.bundle/zh-Hans.lproj/Localizable.strings -------------------------------------------------------------------------------- /ZYQPhotoBrowser/ZYQPhoto.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZYQPhoto.h 3 | // ZYQPhotoBrowser 4 | // 5 | // Created by Michael Waterfall on 17/10/2010. 6 | // Copyright 2010 d3i. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "ZYQPhotoProtocol.h" 11 | #ifdef NODEPENDENCY 12 | #else 13 | #import 14 | #endif 15 | 16 | // This class models a photo/image and it's caption 17 | // If you want to handle photos, caching, decompression 18 | // yourself then you can simply ensure your custom data model 19 | // conforms to ZYQPhotoProtocol 20 | // Progress download block, used to update the progressView 21 | typedef void (^ZYQProgressUpdateBlock)(CGFloat progress); 22 | 23 | @interface ZYQPhoto : NSObject 24 | 25 | // Properties 26 | @property (nonatomic, strong) NSString *caption; 27 | @property (nonatomic, strong) NSURL *photoURL; 28 | @property (nonatomic, strong) ZYQProgressUpdateBlock progressUpdateBlock; 29 | @property (nonatomic, strong) UIImage *placeholderImage; 30 | 31 | // Class 32 | + (ZYQPhoto *)photoWithImage:(UIImage *)image; 33 | + (ZYQPhoto *)photoWithFilePath:(NSString *)path; 34 | + (ZYQPhoto *)photoWithURL:(NSURL *)url; 35 | 36 | + (NSArray *)photosWithImages:(NSArray *)imagesArray; 37 | + (NSArray *)photosWithFilePaths:(NSArray *)pathsArray; 38 | + (NSArray *)photosWithURLs:(NSArray *)urlsArray; 39 | 40 | // Init 41 | - (id)initWithImage:(UIImage *)image; 42 | - (id)initWithFilePath:(NSString *)path; 43 | - (id)initWithURL:(NSURL *)url; 44 | 45 | @end 46 | 47 | -------------------------------------------------------------------------------- /ZYQPhotoBrowser/ZYQPhoto.m: -------------------------------------------------------------------------------- 1 | // 2 | // ZYQPhoto.m 3 | // ZYQPhotoBrowser 4 | // 5 | // Created by Michael Waterfall on 17/10/2010. 6 | // Copyright 2010 d3i. All rights reserved. 7 | // 8 | 9 | #import "ZYQPhoto.h" 10 | #import "ZYQPhotoBrowser.h" 11 | #import 12 | 13 | #ifdef NODEPENDENCY 14 | #else 15 | #import "UIImage+GIF.h" 16 | #import "FLAnimatedImage.h" 17 | #import "NSData+ImageContentType.h" 18 | #endif 19 | 20 | // Private 21 | @interface ZYQPhoto () { 22 | // Image Sources 23 | NSString *_photoPath; 24 | 25 | // Image 26 | UIImage *_underlyingImage; 27 | 28 | // Other 29 | NSString *_caption; 30 | BOOL _loadingInProgress; 31 | } 32 | 33 | // Properties 34 | @property (nonatomic, strong) UIImage *underlyingImage; 35 | 36 | // Methods 37 | - (void)imageLoadingComplete; 38 | 39 | @end 40 | 41 | // ZYQPhoto 42 | @implementation ZYQPhoto 43 | 44 | // Properties 45 | @synthesize underlyingImage = _underlyingImage, 46 | photoURL = _photoURL, 47 | caption = _caption; 48 | 49 | #pragma mark Class Methods 50 | 51 | + (ZYQPhoto *)photoWithImage:(UIImage *)image { 52 | return [[ZYQPhoto alloc] initWithImage:image]; 53 | } 54 | 55 | + (ZYQPhoto *)photoWithFilePath:(NSString *)path { 56 | return [[ZYQPhoto alloc] initWithFilePath:path]; 57 | } 58 | 59 | + (ZYQPhoto *)photoWithURL:(NSURL *)url { 60 | return [[ZYQPhoto alloc] initWithURL:url]; 61 | } 62 | 63 | + (NSArray *)photosWithImages:(NSArray *)imagesArray { 64 | NSMutableArray *photos = [NSMutableArray arrayWithCapacity:imagesArray.count]; 65 | 66 | for (UIImage *image in imagesArray) { 67 | if ([image isKindOfClass:[UIImage class]]) { 68 | ZYQPhoto *photo = [ZYQPhoto photoWithImage:image]; 69 | [photos addObject:photo]; 70 | } 71 | } 72 | 73 | return photos; 74 | } 75 | 76 | + (NSArray *)photosWithFilePaths:(NSArray *)pathsArray { 77 | NSMutableArray *photos = [NSMutableArray arrayWithCapacity:pathsArray.count]; 78 | 79 | for (NSString *path in pathsArray) { 80 | if ([path isKindOfClass:[NSString class]]) { 81 | ZYQPhoto *photo = [ZYQPhoto photoWithFilePath:path]; 82 | [photos addObject:photo]; 83 | } 84 | } 85 | 86 | return photos; 87 | } 88 | 89 | + (NSArray *)photosWithURLs:(NSArray *)urlsArray { 90 | NSMutableArray *photos = [NSMutableArray arrayWithCapacity:urlsArray.count]; 91 | 92 | for (id url in urlsArray) { 93 | if ([url isKindOfClass:[NSURL class]]) { 94 | ZYQPhoto *photo = [ZYQPhoto photoWithURL:url]; 95 | [photos addObject:photo]; 96 | } 97 | else if ([url isKindOfClass:[NSString class]]) { 98 | ZYQPhoto *photo = [ZYQPhoto photoWithURL:[NSURL URLWithString:url]]; 99 | [photos addObject:photo]; 100 | } 101 | } 102 | 103 | return photos; 104 | } 105 | 106 | #pragma mark NSObject 107 | 108 | - (id)initWithImage:(UIImage *)image { 109 | if ((self = [super init])) { 110 | self.underlyingImage = image; 111 | } 112 | return self; 113 | } 114 | 115 | - (id)initWithFilePath:(NSString *)path { 116 | if ((self = [super init])) { 117 | _photoPath = [path copy]; 118 | } 119 | return self; 120 | } 121 | 122 | - (id)initWithURL:(NSURL *)url { 123 | if ((self = [super init])) { 124 | _photoURL = [url copy]; 125 | } 126 | return self; 127 | } 128 | 129 | #pragma mark ZYQPhoto Protocol Methods 130 | 131 | - (UIImage *)underlyingImage { 132 | return _underlyingImage; 133 | } 134 | 135 | - (void)loadUnderlyingImageAndNotify { 136 | NSAssert([[NSThread currentThread] isMainThread], @"This method must be called on the main thread."); 137 | _loadingInProgress = YES; 138 | if (self.underlyingImage) { 139 | // Image already loaded 140 | [self imageLoadingComplete]; 141 | } else { 142 | if (_photoPath) { 143 | // Load async from file 144 | [self performSelectorInBackground:@selector(loadImageFromFileAsync) withObject:nil]; 145 | } else if (_photoURL) { 146 | // Load async from web (using SDWebImageManager) 147 | if ([self conformsToProtocol:objc_getProtocol("ZYQPhotoCustomLoadProtocol")]) { 148 | [(ZYQPhoto*)self loadImageWithURL:_photoURL updateProgressBlock:^(CGFloat progress) { 149 | if (self.progressUpdateBlock) { 150 | self.progressUpdateBlock(progress); 151 | } 152 | } completedBlock:^(UIImage *image, NSError *error) { 153 | if (image) { 154 | self.underlyingImage = image; 155 | } 156 | 157 | [self performSelectorOnMainThread:@selector(imageLoadingComplete) withObject:nil waitUntilDone:NO]; 158 | }]; 159 | } 160 | else{ 161 | #ifdef NODEPENDENCY 162 | #else 163 | [[SDWebImageManager sharedManager] loadImageWithURL:_photoURL options:SDWebImageRetryFailed progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) { 164 | CGFloat progress = ((CGFloat)receivedSize)/((CGFloat)expectedSize); 165 | 166 | if (self.progressUpdateBlock) { 167 | self.progressUpdateBlock(progress); 168 | } 169 | } completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) { 170 | if (image) { 171 | if (image.isGIF) { 172 | self.underlyingImage=(UIImage*)[FLAnimatedImage animatedImageWithGIFData:data]; 173 | } 174 | else{ 175 | self.underlyingImage = image; 176 | } 177 | } 178 | 179 | [self performSelectorOnMainThread:@selector(imageLoadingComplete) withObject:nil waitUntilDone:NO]; 180 | }]; 181 | #endif 182 | } 183 | 184 | 185 | } else { 186 | // Failed - no source 187 | self.underlyingImage = nil; 188 | [self imageLoadingComplete]; 189 | } 190 | } 191 | } 192 | 193 | // Release if we can get it again from path or url 194 | - (void)unloadUnderlyingImage { 195 | _loadingInProgress = NO; 196 | 197 | if (self.underlyingImage && (_photoPath || _photoURL)) { 198 | self.underlyingImage = nil; 199 | } 200 | } 201 | 202 | #pragma mark - Async Loading 203 | 204 | /*- (UIImage *)decodedImageWithImage:(UIImage *)image { 205 | CGImageRef imageRef = image.CGImage; 206 | // System only supports RGB, set explicitly and prevent context error 207 | // if the downloaded image is not the supported format 208 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 209 | 210 | CGContextRef context = CGBitmapContextCreate(NULL, 211 | CGImageGetWidth(imageRef), 212 | CGImageGetHeight(imageRef), 213 | 8, 214 | // width * 4 will be enough because are in ARGB format, don't read from the image 215 | CGImageGetWidth(imageRef) * 4, 216 | colorSpace, 217 | // kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little 218 | // makes system don't need to do extra conversion when displayed. 219 | kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little); 220 | CGColorSpaceRelease(colorSpace); 221 | 222 | if ( ! context) { 223 | return nil; 224 | } 225 | 226 | CGRect rect = (CGRect){CGPointZero, CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)}; 227 | CGContextDrawImage(context, rect, imageRef); 228 | CGImageRef decompressedImageRef = CGBitmapContextCreateImage(context); 229 | CGContextRelease(context); 230 | 231 | UIImage *decompressedImage = [[UIImage alloc] initWithCGImage:decompressedImageRef]; 232 | CGImageRelease(decompressedImageRef); 233 | return decompressedImage; 234 | }*/ 235 | 236 | - (UIImage *)decodedImageWithImage:(UIImage *)image { 237 | if (![image isKindOfClass:[UIImage class]]||image.images) { 238 | // Do not decode animated images 239 | return image; 240 | } 241 | 242 | CGImageRef imageRef = image.CGImage; 243 | CGSize imageSize = CGSizeMake(CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)); 244 | CGRect imageRect = (CGRect){.origin = CGPointZero, .size = imageSize}; 245 | 246 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 247 | CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef); 248 | 249 | int infoMask = (bitmapInfo & kCGBitmapAlphaInfoMask); 250 | BOOL anyNonAlpha = (infoMask == kCGImageAlphaNone || 251 | infoMask == kCGImageAlphaNoneSkipFirst || 252 | infoMask == kCGImageAlphaNoneSkipLast); 253 | 254 | // CGBitmapContextCreate doesn't support kCGImageAlphaNone with RGB. 255 | // https://developer.apple.com/library/mac/#qa/qa1037/_index.html 256 | if (infoMask == kCGImageAlphaNone && CGColorSpaceGetNumberOfComponents(colorSpace) > 1) 257 | { 258 | // Unset the old alpha info. 259 | bitmapInfo &= ~kCGBitmapAlphaInfoMask; 260 | 261 | // Set noneSkipFirst. 262 | bitmapInfo |= kCGImageAlphaNoneSkipFirst; 263 | } 264 | // Some PNGs tell us they have alpha but only 3 components. Odd. 265 | else if (!anyNonAlpha && CGColorSpaceGetNumberOfComponents(colorSpace) == 3) 266 | { 267 | // Unset the old alpha info. 268 | bitmapInfo &= ~kCGBitmapAlphaInfoMask; 269 | bitmapInfo |= kCGImageAlphaPremultipliedFirst; 270 | } 271 | 272 | // It calculates the bytes-per-row based on the bitsPerComponent and width arguments. 273 | CGContextRef context = CGBitmapContextCreate(NULL, 274 | imageSize.width, 275 | imageSize.height, 276 | CGImageGetBitsPerComponent(imageRef), 277 | 0, 278 | colorSpace, 279 | bitmapInfo); 280 | CGColorSpaceRelease(colorSpace); 281 | 282 | // If failed, return undecompressed image 283 | if (!context) return image; 284 | 285 | CGContextDrawImage(context, imageRect, imageRef); 286 | CGImageRef decompressedImageRef = CGBitmapContextCreateImage(context); 287 | 288 | CGContextRelease(context); 289 | 290 | UIImage *decompressedImage = [UIImage imageWithCGImage:decompressedImageRef scale:image.scale orientation:image.imageOrientation]; 291 | CGImageRelease(decompressedImageRef); 292 | return decompressedImage; 293 | } 294 | 295 | // Called in background 296 | // Load image in background from local file 297 | - (void)loadImageFromFileAsync { 298 | @autoreleasepool { 299 | @try { 300 | if ([self conformsToProtocol:objc_getProtocol("ZYQPhotoCustomLoadProtocol")]) { 301 | self.underlyingImage=[(ZYQPhoto*)self loadImageWithFile:_photoPath]; 302 | } 303 | else{ 304 | self.underlyingImage = [UIImage imageWithContentsOfFile:_photoPath]; 305 | #ifdef NODEPENDENCY 306 | #else 307 | NSData *imageData=[NSData dataWithContentsOfFile:_photoPath]; 308 | if (self.underlyingImage.isGIF||SDImageFormatGIF== [NSData sd_imageFormatForImageData:imageData]) { 309 | self.underlyingImage=(UIImage*)[FLAnimatedImage animatedImageWithGIFData:imageData]; 310 | } 311 | 312 | #endif 313 | } 314 | 315 | if (!_underlyingImage) { 316 | ZYQLog(@"Error loading photo from path: %@", _photoPath); 317 | } 318 | } @finally { 319 | if (![self conformsToProtocol:objc_getProtocol("ZYQPhotoCustomLoadProtocol")]) { 320 | self.underlyingImage = [self decodedImageWithImage: self.underlyingImage]; 321 | } 322 | [self performSelectorOnMainThread:@selector(imageLoadingComplete) withObject:nil waitUntilDone:NO]; 323 | } 324 | } 325 | } 326 | 327 | // Called on main 328 | - (void)imageLoadingComplete { 329 | NSAssert([[NSThread currentThread] isMainThread], @"This method must be called on the main thread."); 330 | // Complete so notify 331 | _loadingInProgress = NO; 332 | [[NSNotificationCenter defaultCenter] postNotificationName:ZYQPhoto_LOADING_DID_END_NOTIFICATION 333 | object:self]; 334 | } 335 | 336 | @end 337 | -------------------------------------------------------------------------------- /ZYQPhotoBrowser/ZYQPhotoBrowser.bundle/images/IDMPhotoBrowser_arrowLeft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heroims/ZYQPhotoBrowser/0bfa3ce3a5cf5ce4a452bf1c5d5335a066a4a367/ZYQPhotoBrowser/ZYQPhotoBrowser.bundle/images/IDMPhotoBrowser_arrowLeft.png -------------------------------------------------------------------------------- /ZYQPhotoBrowser/ZYQPhotoBrowser.bundle/images/IDMPhotoBrowser_arrowLeft@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heroims/ZYQPhotoBrowser/0bfa3ce3a5cf5ce4a452bf1c5d5335a066a4a367/ZYQPhotoBrowser/ZYQPhotoBrowser.bundle/images/IDMPhotoBrowser_arrowLeft@2x.png -------------------------------------------------------------------------------- /ZYQPhotoBrowser/ZYQPhotoBrowser.bundle/images/IDMPhotoBrowser_arrowRight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heroims/ZYQPhotoBrowser/0bfa3ce3a5cf5ce4a452bf1c5d5335a066a4a367/ZYQPhotoBrowser/ZYQPhotoBrowser.bundle/images/IDMPhotoBrowser_arrowRight.png -------------------------------------------------------------------------------- /ZYQPhotoBrowser/ZYQPhotoBrowser.bundle/images/IDMPhotoBrowser_arrowRight@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heroims/ZYQPhotoBrowser/0bfa3ce3a5cf5ce4a452bf1c5d5335a066a4a367/ZYQPhotoBrowser/ZYQPhotoBrowser.bundle/images/IDMPhotoBrowser_arrowRight@2x.png -------------------------------------------------------------------------------- /ZYQPhotoBrowser/ZYQPhotoBrowser.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZYQPhotoBrowser.h 3 | // ZYQPhotoBrowser 4 | // 5 | // Created by Michael Waterfall on 14/10/2010. 6 | // Copyright 2010 d3i. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | #import "ZYQPhoto.h" 13 | #import "ZYQPhotoProtocol.h" 14 | #import "ZYQCaptionView.h" 15 | #import "ZYQTapDetectingImageView.h" 16 | 17 | // Delgate 18 | @class ZYQPhotoBrowser; 19 | @protocol ZYQPhotoBrowserDelegate 20 | @optional 21 | - (void)willAppearPhotoBrowser:(ZYQPhotoBrowser *)photoBrowser; 22 | - (void)willDisappearPhotoBrowser:(ZYQPhotoBrowser *)photoBrowser; 23 | - (void)photoBrowser:(ZYQPhotoBrowser *)photoBrowser didShowPhotoAtIndex:(NSUInteger)index; 24 | - (void)photoBrowser:(ZYQPhotoBrowser *)photoBrowser didDismissAtPageIndex:(NSUInteger)index; 25 | - (void)photoBrowser:(ZYQPhotoBrowser *)photoBrowser willDismissAtPageIndex:(NSUInteger)index; 26 | - (void)photoBrowser:(ZYQPhotoBrowser *)photoBrowser didDismissActionSheetWithButtonIndex:(NSUInteger)buttonIndex photoIndex:(NSUInteger)photoIndex; 27 | - (NSString *)photoBrowser:(ZYQPhotoBrowser *)photoBrowser counterForPhotoAtIndex:(NSUInteger)index; 28 | - (NSString *)photoBrowser:(ZYQPhotoBrowser *)photoBrowser titleForPhotoAtIndex:(NSUInteger)index; 29 | 30 | - (ZYQCaptionView *)photoBrowser:(ZYQPhotoBrowser *)photoBrowser captionViewForPhotoAtIndex:(NSUInteger)index; 31 | - (void)photoBrowser:(ZYQPhotoBrowser *)photoBrowser imageFailed:(NSUInteger)index imageView:(ZYQTapDetectingImageView *)imageView; 32 | @end 33 | 34 | // ZYQPhotoBrowser 35 | @interface ZYQPhotoBrowser : UIViewController 36 | 37 | // Properties 38 | @property (nonatomic, strong) id delegate; 39 | 40 | // Toolbar customization 41 | @property (nonatomic) BOOL displayToolbar; 42 | @property (nonatomic) BOOL displayCounterLabel; 43 | @property (nonatomic) BOOL displayArrowButton; 44 | @property (nonatomic) BOOL displayActionButton; 45 | @property (nonatomic, strong) NSArray *actionButtonTitles; 46 | @property (nonatomic, weak) UIImage *leftArrowImage, *leftArrowSelectedImage; 47 | @property (nonatomic, weak) UIImage *rightArrowImage, *rightArrowSelectedImage; 48 | @property (nonatomic, weak) UIImage *actionButtonImage, *actionButtonSelectedImage; 49 | 50 | // View customization 51 | @property (nonatomic) BOOL displayDoneButton; 52 | @property (nonatomic) BOOL displayTitle; 53 | @property (nonatomic) BOOL useWhiteBackgroundColor; 54 | @property (nonatomic, weak) UIImage *doneButtonImage; 55 | @property (nonatomic, weak) UIColor *trackTintColor, *progressTintColor; 56 | @property (nonatomic, assign) CGFloat doneButtonRightInset, doneButtonTopInset; 57 | @property (nonatomic, assign) CGSize doneButtonSize; 58 | @property (nonatomic, strong) UIView *customBackgroud; 59 | 60 | @property (nonatomic, assign) Class gifSupportImageViewClass; 61 | 62 | @property (nonatomic, weak) UIImage *scaleImage; 63 | 64 | @property (nonatomic) BOOL arrowButtonsChangePhotosAnimated; 65 | 66 | @property (nonatomic) BOOL forceHideStatusBar; 67 | @property (nonatomic) BOOL useZoomAnimation; 68 | @property (nonatomic) BOOL disableVerticalSwipe; 69 | 70 | @property (nonatomic) BOOL dismissOnTouch; 71 | 72 | // Default value: true 73 | // Set to false to tell the photo viewer not to hide the interface when scrolling 74 | @property (nonatomic) BOOL autoHideInterface; 75 | 76 | // Defines zooming of the background (default 1.0) 77 | @property (nonatomic) float backgroundScaleFactor; 78 | 79 | // Animation time (default .28) 80 | @property (nonatomic) float animationDuration; 81 | 82 | // Init 83 | - (id)initWithPhotos:(NSArray *)photosArray; 84 | 85 | // Init (animated from view) 86 | - (id)initWithPhotos:(NSArray *)photosArray animatedFromView:(UIView*)view; 87 | 88 | // Init with NSURL objects 89 | - (id)initWithPhotoURLs:(NSArray *)photoURLsArray; 90 | 91 | // Init with NSURL objects (animated from view) 92 | - (id)initWithPhotoURLs:(NSArray *)photoURLsArray animatedFromView:(UIView*)view; 93 | 94 | // Reloads the photo browser and refetches data 95 | - (void)reloadData; 96 | 97 | // Set page that photo browser starts on 98 | - (void)setInitialPageIndex:(NSUInteger)index; 99 | 100 | // Get ZYQPhoto at index 101 | - (id)photoAtIndex:(NSUInteger)index; 102 | 103 | // Get Photos 104 | @property(nonatomic,readonly)NSArray *photos; 105 | 106 | @end 107 | -------------------------------------------------------------------------------- /ZYQPhotoBrowser/ZYQPhotoBrowser.m: -------------------------------------------------------------------------------- 1 | // 2 | // ZYQPhotoBrowser.m 3 | // ZYQPhotoBrowser 4 | // 5 | // Created by Michael Waterfall on 14/10/2010. 6 | // Copyright 2010 d3i. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "ZYQPhotoBrowser.h" 11 | #import "ZYQZoomingScrollView.h" 12 | #import 13 | #import 14 | 15 | #ifndef ZYQPhotoBrowserLocalizedStrings 16 | #define ZYQPhotoBrowserLocalizedStrings(key) \ 17 | NSLocalizedStringFromTableInBundle((key), nil, [NSBundle bundleWithPath:[[NSBundle bundleForClass: [ZYQPhotoBrowser class]] pathForResource:@"ZYQPBLocalizations" ofType:@"bundle"]], nil) 18 | #endif 19 | 20 | // Private 21 | @interface ZYQPhotoBrowser () { 22 | // Data 23 | NSMutableArray *_photos; 24 | 25 | // Views 26 | UIScrollView *_pagingScrollView; 27 | 28 | // Gesture 29 | UIPanGestureRecognizer *_panGesture; 30 | 31 | // Paging 32 | NSMutableSet *_visiblePages, *_recycledPages; 33 | NSUInteger _pageIndexBeforeRotation; 34 | NSUInteger _currentPageIndex; 35 | 36 | // Buttons 37 | UIButton *_doneButton; 38 | 39 | // Title 40 | UILabel *_titleLabel; 41 | 42 | // Toolbar 43 | UIToolbar *_toolbar; 44 | UIBarButtonItem *_previousButton, *_nextButton, *_actionButton; 45 | UIBarButtonItem *_counterButton; 46 | UILabel *_counterLabel; 47 | 48 | // Actions 49 | #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 50 | UIAlertController *_actionsSheet; 51 | #else 52 | UIActionSheet *_actionsSheet; 53 | #endif 54 | UIActivityViewController *activityViewController; 55 | 56 | // Control 57 | NSTimer *_controlVisibilityTimer; 58 | 59 | // Appearance 60 | //UIStatusBarStyle _previousStatusBarStyle; 61 | BOOL _statusBarOriginallyHidden; 62 | 63 | // Present 64 | UIView *_senderViewForAnimation; 65 | 66 | // Misc 67 | BOOL _performingLayout; 68 | BOOL _rotating; 69 | BOOL _viewIsActive; // active as in it's in the view heirarchy 70 | BOOL _autoHide; 71 | NSInteger _initalPageIndex; 72 | 73 | BOOL _isdraggingPhoto; 74 | 75 | CGRect _senderViewOriginalFrame; 76 | //UIImage *_backgroundScreenshot; 77 | 78 | UIWindow *_applicationWindow; 79 | 80 | // iOS 7 81 | UIViewController *_applicationTopViewController; 82 | int _previousModalPresentationStyle; 83 | } 84 | 85 | // Private Properties 86 | #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 87 | @property (nonatomic, strong) UIAlertController *actionsSheet; 88 | #else 89 | @property (nonatomic, strong) UIActionSheet *actionsSheet; 90 | #endif 91 | @property (nonatomic, strong) UIActivityViewController *activityViewController; 92 | 93 | // Private Methods 94 | 95 | // Layout 96 | - (void)performLayout; 97 | 98 | // Paging 99 | - (void)tilePages; 100 | - (BOOL)isDisplayingPageForIndex:(NSUInteger)index; 101 | - (ZYQZoomingScrollView *)pageDisplayedAtIndex:(NSUInteger)index; 102 | - (ZYQZoomingScrollView *)pageDisplayingPhoto:(id)photo; 103 | - (ZYQZoomingScrollView *)dequeueRecycledPage; 104 | - (void)configurePage:(ZYQZoomingScrollView *)page forIndex:(NSUInteger)index; 105 | - (void)didStartViewingPageAtIndex:(NSUInteger)index; 106 | 107 | // Frames 108 | - (CGRect)frameForPagingScrollView; 109 | - (CGRect)frameForPageAtIndex:(NSUInteger)index; 110 | - (CGSize)contentSizeForPagingScrollView; 111 | - (CGPoint)contentOffsetForPageAtIndex:(NSUInteger)index; 112 | - (CGRect)frameForToolbarAtOrientation:(UIInterfaceOrientation)orientation; 113 | - (CGRect)frameForDoneButtonAtOrientation:(UIInterfaceOrientation)orientation; 114 | - (CGRect)frameForCaptionView:(ZYQCaptionView *)captionView atIndex:(NSUInteger)index; 115 | 116 | // Toolbar 117 | - (void)updateToolbar; 118 | 119 | // Navigation 120 | - (void)jumpToPageAtIndex:(NSUInteger)index; 121 | - (void)gotoPreviousPage; 122 | - (void)gotoNextPage; 123 | 124 | // Controls 125 | - (void)cancelControlHiding; 126 | - (void)hideControlsAfterDelay; 127 | - (void)setControlsHidden:(BOOL)hidden animated:(BOOL)animated permanent:(BOOL)permanent; 128 | //- (void)toggleControls; 129 | - (BOOL)areControlsHidden; 130 | 131 | // Interactions 132 | - (void)handleSingleTap; 133 | 134 | // Data 135 | - (NSUInteger)numberOfPhotos; 136 | - (id)photoAtIndex:(NSUInteger)index; 137 | - (UIImage *)imageForPhoto:(id)photo; 138 | - (void)loadAdjacentPhotosIfNecessary:(id)photo; 139 | - (void)releaseAllUnderlyingPhotos; 140 | 141 | @end 142 | 143 | // ZYQPhotoBrowser 144 | @implementation ZYQPhotoBrowser 145 | 146 | // Properties 147 | @synthesize displayDoneButton = _displayDoneButton, displayTitle=_displayTitle, displayToolbar = _displayToolbar, displayActionButton = _displayActionButton, displayCounterLabel = _displayCounterLabel, useWhiteBackgroundColor = _useWhiteBackgroundColor, doneButtonImage = _doneButtonImage; 148 | @synthesize leftArrowImage = _leftArrowImage, rightArrowImage = _rightArrowImage, leftArrowSelectedImage = _leftArrowSelectedImage, rightArrowSelectedImage = _rightArrowSelectedImage, actionButtonImage = _actionButtonImage, actionButtonSelectedImage = _actionButtonSelectedImage; 149 | @synthesize displayArrowButton = _displayArrowButton, actionButtonTitles = _actionButtonTitles; 150 | @synthesize arrowButtonsChangePhotosAnimated = _arrowButtonsChangePhotosAnimated; 151 | @synthesize forceHideStatusBar = _forceHideStatusBar; 152 | @synthesize useZoomAnimation = _useZoomAnimation; 153 | @synthesize disableVerticalSwipe = _disableVerticalSwipe; 154 | @synthesize dismissOnTouch = _dismissOnTouch; 155 | @synthesize actionsSheet = _actionsSheet, activityViewController = _activityViewController; 156 | @synthesize customBackgroud = _customBackgroud; 157 | @synthesize gifSupportImageViewClass = _gifSupportImageViewClass; 158 | @synthesize trackTintColor = _trackTintColor, progressTintColor = _progressTintColor; 159 | @synthesize delegate = _delegate; 160 | 161 | #pragma mark - NSObject 162 | 163 | - (id)init { 164 | if ((self = [super init])) { 165 | // Defaults 166 | self.hidesBottomBarWhenPushed = YES; 167 | 168 | _currentPageIndex = 0; 169 | _performingLayout = NO; // Reset on view did appear 170 | _rotating = NO; 171 | _viewIsActive = NO; 172 | _visiblePages = [NSMutableSet new]; 173 | _recycledPages = [NSMutableSet new]; 174 | _photos = [NSMutableArray new]; 175 | 176 | _initalPageIndex = 0; 177 | _autoHide = YES; 178 | _autoHideInterface = YES; 179 | 180 | _displayDoneButton = YES; 181 | _displayTitle=NO; 182 | _doneButtonImage = nil; 183 | 184 | _displayToolbar = YES; 185 | _displayActionButton = YES; 186 | _displayArrowButton = YES; 187 | _displayCounterLabel = NO; 188 | 189 | _forceHideStatusBar = NO; 190 | _useZoomAnimation = NO; 191 | _disableVerticalSwipe = NO; 192 | 193 | _dismissOnTouch = NO; 194 | 195 | _useWhiteBackgroundColor = NO; 196 | _leftArrowImage = _rightArrowImage = _leftArrowSelectedImage = _rightArrowSelectedImage = nil; 197 | 198 | _arrowButtonsChangePhotosAnimated = YES; 199 | 200 | _backgroundScaleFactor = 1.0; 201 | _animationDuration = 0.28; 202 | _senderViewForAnimation = nil; 203 | _scaleImage = nil; 204 | 205 | _isdraggingPhoto = NO; 206 | 207 | _doneButtonRightInset = 20.f; 208 | _doneButtonTopInset = 30.f; 209 | _doneButtonSize = CGSizeMake(55.f, 26.f); 210 | 211 | if ([self respondsToSelector:@selector(automaticallyAdjustsScrollViewInsets)]) { 212 | self.automaticallyAdjustsScrollViewInsets = NO; 213 | } 214 | 215 | _applicationWindow = [[[UIApplication sharedApplication] delegate] window]; 216 | self.modalPresentationStyle = UIModalPresentationCustom; 217 | self.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; 218 | self.modalPresentationCapturesStatusBarAppearance = YES; 219 | self.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; 220 | 221 | // Listen for ZYQPhoto notifications 222 | [[NSNotificationCenter defaultCenter] addObserver:self 223 | selector:@selector(handleZYQPhotoLoadingDidEndNotification:) 224 | name:ZYQPhoto_LOADING_DID_END_NOTIFICATION 225 | object:nil]; 226 | } 227 | 228 | return self; 229 | } 230 | 231 | - (id)initWithPhotos:(NSArray *)photosArray { 232 | if ((self = [self init])) { 233 | _photos = [[NSMutableArray alloc] initWithArray:photosArray]; 234 | } 235 | return self; 236 | } 237 | 238 | - (id)initWithPhotos:(NSArray *)photosArray animatedFromView:(UIView*)view { 239 | if ((self = [self init])) { 240 | _photos = [[NSMutableArray alloc] initWithArray:photosArray]; 241 | _senderViewForAnimation = view; 242 | } 243 | return self; 244 | } 245 | 246 | - (id)initWithPhotoURLs:(NSArray *)photoURLsArray { 247 | if ((self = [self init])) { 248 | NSArray *photosArray = [ZYQPhoto photosWithURLs:photoURLsArray]; 249 | _photos = [[NSMutableArray alloc] initWithArray:photosArray]; 250 | } 251 | return self; 252 | } 253 | 254 | - (id)initWithPhotoURLs:(NSArray *)photoURLsArray animatedFromView:(UIView*)view { 255 | if ((self = [self init])) { 256 | NSArray *photosArray = [ZYQPhoto photosWithURLs:photoURLsArray]; 257 | _photos = [[NSMutableArray alloc] initWithArray:photosArray]; 258 | _senderViewForAnimation = view; 259 | } 260 | return self; 261 | } 262 | 263 | - (void)dealloc { 264 | _pagingScrollView.delegate = nil; 265 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 266 | [self releaseAllUnderlyingPhotos]; 267 | } 268 | 269 | - (void)releaseAllUnderlyingPhotos { 270 | for (id p in _photos) { if (p != [NSNull null]) [p unloadUnderlyingImage]; } // Release photos 271 | } 272 | 273 | - (void)didReceiveMemoryWarning { 274 | // Release any cached data, images, etc that aren't in use. 275 | [self releaseAllUnderlyingPhotos]; 276 | [_recycledPages removeAllObjects]; 277 | 278 | // Releases the view if it doesn't have a superview. 279 | [super didReceiveMemoryWarning]; 280 | } 281 | 282 | #pragma mark - Pan Gesture 283 | 284 | - (void)panGestureRecognized:(id)sender { 285 | // Initial Setup 286 | ZYQZoomingScrollView *scrollView = [self pageDisplayedAtIndex:_currentPageIndex]; 287 | //ZYQTapDetectingImageView *scrollView.photoImageView = scrollView.photoImageView; 288 | 289 | static float firstX, firstY; 290 | 291 | float viewHeight = scrollView.frame.size.height; 292 | float viewHalfHeight = viewHeight/2; 293 | 294 | CGPoint translatedPoint = [(UIPanGestureRecognizer*)sender translationInView:self.view]; 295 | 296 | // Gesture Began 297 | if ([(UIPanGestureRecognizer*)sender state] == UIGestureRecognizerStateBegan) { 298 | [self setControlsHidden:YES animated:YES permanent:YES]; 299 | 300 | firstX = [scrollView center].x; 301 | firstY = [scrollView center].y; 302 | 303 | _senderViewForAnimation.hidden = (_currentPageIndex == _initalPageIndex); 304 | 305 | _isdraggingPhoto = YES; 306 | [self setNeedsStatusBarAppearanceUpdate]; 307 | } 308 | 309 | translatedPoint = CGPointMake(firstX, firstY+translatedPoint.y); 310 | [scrollView setCenter:translatedPoint]; 311 | 312 | float newY = scrollView.center.y - viewHalfHeight; 313 | float newAlpha = 1 - fabsf(newY)/viewHeight; //abs(newY)/viewHeight * 1.8; 314 | 315 | self.view.opaque = YES; 316 | 317 | self.view.backgroundColor = [UIColor colorWithWhite:(_useWhiteBackgroundColor ? 1 : 0) alpha:newAlpha]; 318 | 319 | // Gesture Ended 320 | if ([(UIPanGestureRecognizer*)sender state] == UIGestureRecognizerStateEnded) { 321 | if(scrollView.center.y > viewHalfHeight+40 || scrollView.center.y < viewHalfHeight-40) // Automatic Dismiss View 322 | { 323 | if (_senderViewForAnimation && _currentPageIndex == _initalPageIndex) { 324 | [self performCloseAnimationWithScrollView:scrollView]; 325 | return; 326 | } 327 | 328 | CGFloat finalX = firstX, finalY; 329 | 330 | CGFloat windowsHeigt = [_applicationWindow frame].size.height; 331 | 332 | if(scrollView.center.y > viewHalfHeight+30) // swipe down 333 | finalY = windowsHeigt*2; 334 | else // swipe up 335 | finalY = -viewHalfHeight; 336 | 337 | CGFloat animationDuration = 0.35; 338 | 339 | [UIView beginAnimations:nil context:NULL]; 340 | [UIView setAnimationDuration:animationDuration]; 341 | [UIView setAnimationCurve:UIViewAnimationCurveEaseIn]; 342 | [UIView setAnimationDelegate:self]; 343 | [scrollView setCenter:CGPointMake(finalX, finalY)]; 344 | self.view.backgroundColor = [UIColor colorWithWhite:0 alpha:0]; 345 | [UIView commitAnimations]; 346 | 347 | [self performSelector:@selector(doneButtonPressed:) withObject:self afterDelay:animationDuration]; 348 | } 349 | else // Continue Showing View 350 | { 351 | _isdraggingPhoto = NO; 352 | [self setNeedsStatusBarAppearanceUpdate]; 353 | [self setControlsHidden:NO animated:YES permanent:YES]; 354 | 355 | self.view.backgroundColor = [UIColor colorWithWhite:(_useWhiteBackgroundColor ? 1 : 0) alpha:1]; 356 | 357 | CGFloat velocityY = (.35*[(UIPanGestureRecognizer*)sender velocityInView:self.view].y); 358 | 359 | CGFloat finalX = firstX; 360 | CGFloat finalY = viewHalfHeight; 361 | 362 | CGFloat animationDuration = (ABS(velocityY)*.0002)+.2; 363 | 364 | [UIView beginAnimations:nil context:NULL]; 365 | [UIView setAnimationDuration:animationDuration]; 366 | [UIView setAnimationCurve:UIViewAnimationCurveEaseOut]; 367 | [UIView setAnimationDelegate:self]; 368 | [scrollView setCenter:CGPointMake(finalX, finalY)]; 369 | [UIView commitAnimations]; 370 | } 371 | } 372 | } 373 | 374 | #pragma mark - Animation 375 | 376 | - (void)performPresentAnimation { 377 | self.view.alpha = 0.0f; 378 | _pagingScrollView.alpha = 0.0f; 379 | 380 | UIImage *imageFromView = _scaleImage ? _scaleImage : [self getImageFromView:_senderViewForAnimation]; 381 | 382 | _senderViewOriginalFrame = [_senderViewForAnimation.superview convertRect:_senderViewForAnimation.frame toView:nil]; 383 | 384 | UIView *fadeView = [[UIView alloc] initWithFrame:_applicationWindow.bounds]; 385 | fadeView.backgroundColor = [UIColor clearColor]; 386 | [_applicationWindow addSubview:fadeView]; 387 | 388 | UIImageView *resizableImageView = [[UIImageView alloc] initWithImage:imageFromView]; 389 | resizableImageView.frame = _senderViewOriginalFrame; 390 | resizableImageView.clipsToBounds = YES; 391 | resizableImageView.contentMode = _senderViewForAnimation ? _senderViewForAnimation.contentMode : UIViewContentModeScaleAspectFill; 392 | resizableImageView.backgroundColor = [UIColor clearColor]; 393 | [_applicationWindow addSubview:resizableImageView]; 394 | _senderViewForAnimation.hidden = YES; 395 | 396 | void (^completion)() = ^() { 397 | self.view.alpha = 1.0f; 398 | _pagingScrollView.alpha = 1.0f; 399 | resizableImageView.backgroundColor = [UIColor colorWithWhite:(_useWhiteBackgroundColor) ? 1 : 0 alpha:1]; 400 | [fadeView removeFromSuperview]; 401 | [resizableImageView removeFromSuperview]; 402 | }; 403 | 404 | [UIView animateWithDuration:_animationDuration animations:^{ 405 | fadeView.backgroundColor = self.useWhiteBackgroundColor ? [UIColor whiteColor] : [UIColor blackColor]; 406 | } completion:nil]; 407 | 408 | CGRect finalImageViewFrame = [self animationFrameForImage:imageFromView presenting:YES scrollView:nil]; 409 | 410 | if(_useZoomAnimation) 411 | { 412 | [self animateView:resizableImageView 413 | toFrame:finalImageViewFrame 414 | completion:completion]; 415 | } 416 | else 417 | { 418 | [UIView animateWithDuration:_animationDuration animations:^{ 419 | resizableImageView.layer.frame = finalImageViewFrame; 420 | } completion:^(BOOL finished) { 421 | completion(); 422 | }]; 423 | } 424 | } 425 | 426 | - (void)performCloseAnimationWithScrollView:(ZYQZoomingScrollView*)scrollView { 427 | if ([_delegate respondsToSelector:@selector(willDisappearPhotoBrowser:)]) { 428 | [_delegate willDisappearPhotoBrowser:self]; 429 | } 430 | 431 | float fadeAlpha = 1 - fabs(scrollView.frame.origin.y)/scrollView.frame.size.height; 432 | 433 | UIImage *imageFromView = [scrollView.photo underlyingImage]; 434 | if (!imageFromView && [scrollView.photo respondsToSelector:@selector(placeholderImage)]) { 435 | imageFromView = [scrollView.photo placeholderImage]; 436 | } 437 | 438 | UIView *fadeView = [[UIView alloc] initWithFrame:_applicationWindow.bounds]; 439 | fadeView.backgroundColor = self.useWhiteBackgroundColor ? [UIColor whiteColor] : [UIColor blackColor]; 440 | fadeView.alpha = fadeAlpha; 441 | [_applicationWindow addSubview:fadeView]; 442 | 443 | CGRect imageViewFrame = [self animationFrameForImage:imageFromView presenting:NO scrollView:scrollView]; 444 | 445 | UIImageView *resizableImageView = [[UIImageView alloc] initWithImage:imageFromView]; 446 | resizableImageView.frame = imageViewFrame; 447 | resizableImageView.contentMode = _senderViewForAnimation ? _senderViewForAnimation.contentMode : UIViewContentModeScaleAspectFill; 448 | resizableImageView.backgroundColor = [UIColor clearColor]; 449 | resizableImageView.clipsToBounds = YES; 450 | [_applicationWindow addSubview:resizableImageView]; 451 | self.view.hidden = YES; 452 | 453 | void (^completion)() = ^() { 454 | _senderViewForAnimation.hidden = NO; 455 | _senderViewForAnimation = nil; 456 | _scaleImage = nil; 457 | 458 | [fadeView removeFromSuperview]; 459 | [resizableImageView removeFromSuperview]; 460 | 461 | [self prepareForClosePhotoBrowser]; 462 | [self dismissPhotoBrowserAnimated:NO]; 463 | }; 464 | 465 | [UIView animateWithDuration:_animationDuration animations:^{ 466 | fadeView.alpha = 0; 467 | self.view.backgroundColor = [UIColor clearColor]; 468 | } completion:nil]; 469 | 470 | CGRect senderViewOriginalFrame = _senderViewForAnimation.superview ? [_senderViewForAnimation.superview convertRect:_senderViewForAnimation.frame toView:nil] : _senderViewOriginalFrame; 471 | 472 | if(_useZoomAnimation) 473 | { 474 | [self animateView:resizableImageView 475 | toFrame:senderViewOriginalFrame 476 | completion:completion]; 477 | } 478 | else 479 | { 480 | [UIView animateWithDuration:_animationDuration animations:^{ 481 | resizableImageView.layer.frame = senderViewOriginalFrame; 482 | } completion:^(BOOL finished) { 483 | completion(); 484 | }]; 485 | } 486 | } 487 | 488 | - (CGRect)animationFrameForImage:(UIImage *)image presenting:(BOOL)presenting scrollView:(UIScrollView *)scrollView 489 | { 490 | if (!image) { 491 | return CGRectZero; 492 | } 493 | 494 | CGSize imageSize = image.size; 495 | 496 | CGFloat maxWidth = CGRectGetWidth(_applicationWindow.bounds); 497 | CGFloat maxHeight = CGRectGetHeight(_applicationWindow.bounds); 498 | 499 | CGRect animationFrame = CGRectZero; 500 | 501 | CGFloat aspect = imageSize.width / imageSize.height; 502 | if (maxWidth / aspect <= maxHeight) { 503 | animationFrame.size = CGSizeMake(maxWidth, maxWidth / aspect); 504 | } 505 | else { 506 | animationFrame.size = CGSizeMake(maxHeight * aspect, maxHeight); 507 | } 508 | 509 | animationFrame.origin.x = roundf((maxWidth - animationFrame.size.width) / 2.0f); 510 | animationFrame.origin.y = roundf((maxHeight - animationFrame.size.height) / 2.0f); 511 | 512 | if (!presenting) { 513 | animationFrame.origin.y += scrollView.frame.origin.y; 514 | } 515 | 516 | return animationFrame; 517 | } 518 | 519 | #pragma mark - Genaral 520 | 521 | - (void)prepareForClosePhotoBrowser { 522 | // Gesture 523 | [_applicationWindow removeGestureRecognizer:_panGesture]; 524 | 525 | _autoHide = NO; 526 | 527 | // Controls 528 | [NSObject cancelPreviousPerformRequestsWithTarget:self]; // Cancel any pending toggles from taps 529 | } 530 | 531 | - (void)dismissPhotoBrowserAnimated:(BOOL)animated { 532 | self.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; 533 | 534 | if ([_delegate respondsToSelector:@selector(photoBrowser:willDismissAtPageIndex:)]) 535 | [_delegate photoBrowser:self willDismissAtPageIndex:_currentPageIndex]; 536 | 537 | [self dismissViewControllerAnimated:animated completion:^{ 538 | if ([_delegate respondsToSelector:@selector(photoBrowser:didDismissAtPageIndex:)]) 539 | [_delegate photoBrowser:self didDismissAtPageIndex:_currentPageIndex]; 540 | 541 | // if (SYSTEM_VERSION_LESS_THAN(@"8.0")) 542 | // { 543 | // _applicationTopViewController.modalPresentationStyle = _previousModalPresentationStyle; 544 | // } 545 | }]; 546 | } 547 | 548 | - (UIButton*)customToolbarButtonImage:(UIImage*)image imageSelected:(UIImage*)selectedImage action:(SEL)action { 549 | UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; 550 | [button setImage:image forState:UIControlStateNormal]; 551 | [button setImage:selectedImage forState:UIControlStateDisabled]; 552 | [button addTarget:self action:action forControlEvents:UIControlEventTouchUpInside]; 553 | [button setContentMode:UIViewContentModeCenter]; 554 | [button setFrame:[self getToolbarButtonFrame:image]]; 555 | return button; 556 | } 557 | 558 | - (CGRect)getToolbarButtonFrame:(UIImage *)image{ 559 | BOOL const isRetinaHd = ((float)[[UIScreen mainScreen] scale] > 2.0f); 560 | float const defaultButtonSize = isRetinaHd ? 66.0f : 44.0f; 561 | CGFloat buttonWidth = (image.size.width > defaultButtonSize) ? image.size.width : defaultButtonSize; 562 | CGFloat buttonHeight = (image.size.height > defaultButtonSize) ? image.size.width : defaultButtonSize; 563 | return CGRectMake(0,0, buttonWidth, buttonHeight); 564 | } 565 | 566 | - (UIImage*)getImageFromView:(UIView *)view { 567 | UIGraphicsBeginImageContextWithOptions(view.bounds.size, YES, 2); 568 | [view.layer renderInContext:UIGraphicsGetCurrentContext()]; 569 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 570 | UIGraphicsEndImageContext(); 571 | return image; 572 | } 573 | 574 | - (UIViewController *)topviewController 575 | { 576 | UIViewController *topviewController = [UIApplication sharedApplication].keyWindow.rootViewController; 577 | 578 | while (topviewController.presentedViewController) { 579 | topviewController = topviewController.presentedViewController; 580 | } 581 | 582 | return topviewController; 583 | } 584 | 585 | #pragma mark - View Lifecycle 586 | 587 | - (void)viewDidLoad { 588 | // View 589 | self.view.backgroundColor = [UIColor colorWithWhite:(_useWhiteBackgroundColor ? 1 : 0) alpha:1]; 590 | 591 | self.view.clipsToBounds = YES; 592 | 593 | // Setup paging scrolling view 594 | CGRect pagingScrollViewFrame = [self frameForPagingScrollView]; 595 | _pagingScrollView = [[UIScrollView alloc] initWithFrame:pagingScrollViewFrame]; 596 | //_pagingScrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 597 | _pagingScrollView.pagingEnabled = YES; 598 | _pagingScrollView.delegate = self; 599 | _pagingScrollView.showsHorizontalScrollIndicator = NO; 600 | _pagingScrollView.showsVerticalScrollIndicator = NO; 601 | _pagingScrollView.backgroundColor = [UIColor clearColor]; 602 | _pagingScrollView.contentSize = [self contentSizeForPagingScrollView]; 603 | [self.view addSubview:_pagingScrollView]; 604 | 605 | // Transition animation 606 | [self performPresentAnimation]; 607 | 608 | UIInterfaceOrientation currentOrientation = [UIApplication sharedApplication].statusBarOrientation; 609 | 610 | // Toolbar 611 | _toolbar = [[UIToolbar alloc] initWithFrame:[self frameForToolbarAtOrientation:currentOrientation]]; 612 | _toolbar.backgroundColor = [UIColor clearColor]; 613 | _toolbar.clipsToBounds = YES; 614 | _toolbar.translucent = YES; 615 | [_toolbar setBackgroundImage:[UIImage new] 616 | forToolbarPosition:UIToolbarPositionAny 617 | barMetrics:UIBarMetricsDefault]; 618 | 619 | // Close Button 620 | _doneButton = [UIButton buttonWithType:UIButtonTypeCustom]; 621 | [_doneButton setFrame:[self frameForDoneButtonAtOrientation:currentOrientation]]; 622 | [_doneButton setAlpha:1.0f]; 623 | [_doneButton addTarget:self action:@selector(doneButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; 624 | 625 | if(!_doneButtonImage) { 626 | [_doneButton setTitleColor:[UIColor colorWithWhite:0.9 alpha:0.9] forState:UIControlStateNormal|UIControlStateHighlighted]; 627 | [_doneButton setTitle:ZYQPhotoBrowserLocalizedStrings(@"Done") forState:UIControlStateNormal]; 628 | [_doneButton.titleLabel setFont:[UIFont boldSystemFontOfSize:11.0f]]; 629 | [_doneButton setBackgroundColor:[UIColor colorWithWhite:0.1 alpha:0.5]]; 630 | _doneButton.layer.cornerRadius = 3.0f; 631 | _doneButton.layer.borderColor = [UIColor colorWithWhite:0.9 alpha:0.9].CGColor; 632 | _doneButton.layer.borderWidth = 1.0f; 633 | _doneButtonSize = _doneButton.frame.size; 634 | } 635 | else { 636 | [_doneButton setImage:_doneButtonImage forState:UIControlStateNormal]; 637 | _doneButton.contentMode = UIViewContentModeScaleAspectFit; 638 | } 639 | 640 | _titleLabel = [[UILabel alloc] init]; 641 | [_titleLabel setFrame:[self frameForTitleLabelAtOrientation:currentOrientation]]; 642 | [_titleLabel setAlpha:1.0f]; 643 | _titleLabel.textAlignment = NSTextAlignmentCenter; 644 | _titleLabel.backgroundColor = [UIColor clearColor]; 645 | [_titleLabel setFont:[UIFont boldSystemFontOfSize:17]]; 646 | if(_useWhiteBackgroundColor == NO) { 647 | _titleLabel.textColor = [UIColor whiteColor]; 648 | _titleLabel.shadowColor = [UIColor darkTextColor]; 649 | _titleLabel.shadowOffset = CGSizeMake(0, 1); 650 | } 651 | else { 652 | _titleLabel.textColor = [UIColor blackColor]; 653 | } 654 | 655 | UIImage *leftButtonImage = (_leftArrowImage == nil) ? 656 | [UIImage imageNamed:@"ZYQPhotoBrowser.bundle/images/ZYQPhotoBrowser_arrowLeft.png"] : _leftArrowImage; 657 | 658 | UIImage *rightButtonImage = (_rightArrowImage == nil) ? 659 | [UIImage imageNamed:@"ZYQPhotoBrowser.bundle/images/ZYQPhotoBrowser_arrowRight.png"] : _rightArrowImage; 660 | 661 | UIImage *leftButtonSelectedImage = (_leftArrowSelectedImage == nil) ? 662 | [UIImage imageNamed:@"ZYQPhotoBrowser.bundle/images/ZYQPhotoBrowser_arrowLeftSelected.png"] : _leftArrowSelectedImage; 663 | 664 | UIImage *rightButtonSelectedImage = (_rightArrowSelectedImage == nil) ? 665 | [UIImage imageNamed:@"ZYQPhotoBrowser.bundle/images/ZYQPhotoBrowser_arrowRightSelected.png"] : _rightArrowSelectedImage; 666 | 667 | // Arrows 668 | _previousButton = [[UIBarButtonItem alloc] initWithCustomView:[self customToolbarButtonImage:leftButtonImage 669 | imageSelected:leftButtonSelectedImage 670 | action:@selector(gotoPreviousPage)]]; 671 | 672 | _nextButton = [[UIBarButtonItem alloc] initWithCustomView:[self customToolbarButtonImage:rightButtonImage 673 | imageSelected:rightButtonSelectedImage 674 | action:@selector(gotoNextPage)]]; 675 | 676 | // Counter Label 677 | _counterLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 95, 40)]; 678 | _counterLabel.textAlignment = NSTextAlignmentCenter; 679 | _counterLabel.backgroundColor = [UIColor clearColor]; 680 | _counterLabel.font = [UIFont fontWithName:@"Helvetica" size:17]; 681 | 682 | if(_useWhiteBackgroundColor == NO) { 683 | _counterLabel.textColor = [UIColor whiteColor]; 684 | _counterLabel.shadowColor = [UIColor darkTextColor]; 685 | _counterLabel.shadowOffset = CGSizeMake(0, 1); 686 | } 687 | else { 688 | _counterLabel.textColor = [UIColor blackColor]; 689 | } 690 | 691 | // Counter Button 692 | _counterButton = [[UIBarButtonItem alloc] initWithCustomView:_counterLabel]; 693 | 694 | // Action Button 695 | if(_actionButtonImage != nil && _actionButtonSelectedImage != nil) { 696 | _actionButton = [[UIBarButtonItem alloc] initWithCustomView:[self customToolbarButtonImage:_actionButtonImage 697 | imageSelected:_actionButtonSelectedImage 698 | action:@selector(actionButtonPressed:)]]; 699 | } 700 | else { 701 | _actionButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction 702 | target:self 703 | action:@selector(actionButtonPressed:)]; 704 | } 705 | 706 | // Gesture 707 | _panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureRecognized:)]; 708 | [_panGesture setMinimumNumberOfTouches:1]; 709 | [_panGesture setMaximumNumberOfTouches:1]; 710 | 711 | // Update 712 | //[self reloadData]; 713 | 714 | // Super 715 | [super viewDidLoad]; 716 | } 717 | 718 | - (void)viewWillAppear:(BOOL)animated { 719 | // Update 720 | [self reloadData]; 721 | 722 | 723 | if ([_delegate respondsToSelector:@selector(willAppearPhotoBrowser:)]) { 724 | [_delegate willAppearPhotoBrowser:self]; 725 | } 726 | 727 | // Super 728 | [super viewWillAppear:animated]; 729 | 730 | // Status Bar 731 | _statusBarOriginallyHidden = [UIApplication sharedApplication].statusBarHidden; 732 | 733 | // Update UI 734 | [self hideControlsAfterDelay]; 735 | } 736 | 737 | - (void)viewDidAppear:(BOOL)animated { 738 | [super viewDidAppear:animated]; 739 | _viewIsActive = YES; 740 | } 741 | 742 | // Release any retained subviews of the main view. 743 | - (void)viewDidUnload { 744 | _currentPageIndex = 0; 745 | _pagingScrollView = nil; 746 | _visiblePages = nil; 747 | _recycledPages = nil; 748 | _toolbar = nil; 749 | _doneButton = nil; 750 | _titleLabel = nil; 751 | _previousButton = nil; 752 | _nextButton = nil; 753 | 754 | [super viewDidUnload]; 755 | } 756 | 757 | #pragma mark - Status Bar 758 | 759 | - (UIStatusBarStyle)preferredStatusBarStyle { 760 | return _useWhiteBackgroundColor ? UIStatusBarStyleDefault : UIStatusBarStyleLightContent; 761 | } 762 | 763 | - (BOOL)prefersStatusBarHidden { 764 | if(_forceHideStatusBar) { 765 | return YES; 766 | } 767 | 768 | if(_isdraggingPhoto) { 769 | if(_statusBarOriginallyHidden) { 770 | return YES; 771 | } 772 | else { 773 | return NO; 774 | } 775 | } 776 | else { 777 | return [self areControlsHidden]; 778 | } 779 | } 780 | 781 | - (UIStatusBarAnimation)preferredStatusBarUpdateAnimation { 782 | return UIStatusBarAnimationFade; 783 | } 784 | 785 | #pragma mark - Layout 786 | 787 | - (void)viewWillLayoutSubviews { 788 | // Flag 789 | _performingLayout = YES; 790 | 791 | UIInterfaceOrientation currentOrientation = [UIApplication sharedApplication].statusBarOrientation; 792 | 793 | // Toolbar 794 | _toolbar.frame = [self frameForToolbarAtOrientation:currentOrientation]; 795 | 796 | // Done button 797 | _doneButton.frame = [self frameForDoneButtonAtOrientation:currentOrientation]; 798 | 799 | // Title label 800 | _titleLabel.frame = [self frameForTitleLabelAtOrientation:currentOrientation]; 801 | 802 | //Custom Backgroud 803 | _customBackgroud.frame=self.view.bounds; 804 | 805 | // Remember index 806 | NSUInteger indexPriorToLayout = _currentPageIndex; 807 | 808 | // Get paging scroll view frame to determine if anything needs changing 809 | CGRect pagingScrollViewFrame = [self frameForPagingScrollView]; 810 | 811 | // Frame needs changing 812 | _pagingScrollView.frame = pagingScrollViewFrame; 813 | 814 | // Recalculate contentSize based on current orientation 815 | _pagingScrollView.contentSize = [self contentSizeForPagingScrollView]; 816 | 817 | // Adjust frames and configuration of each visible page 818 | for (ZYQZoomingScrollView *page in _visiblePages) { 819 | NSUInteger index = PAGE_INDEX(page); 820 | page.frame = [self frameForPageAtIndex:index]; 821 | page.captionView.frame = [self frameForCaptionView:page.captionView atIndex:index]; 822 | [page setMaxMinZoomScalesForCurrentBounds]; 823 | } 824 | 825 | // Adjust contentOffset to preserve page location based on values collected prior to location 826 | _pagingScrollView.contentOffset = [self contentOffsetForPageAtIndex:indexPriorToLayout]; 827 | [self didStartViewingPageAtIndex:_currentPageIndex]; // initial 828 | 829 | // Reset 830 | _currentPageIndex = indexPriorToLayout; 831 | _performingLayout = NO; 832 | 833 | // Super 834 | [super viewWillLayoutSubviews]; 835 | } 836 | 837 | - (void)performLayout { 838 | // Setup 839 | _performingLayout = YES; 840 | NSUInteger numberOfPhotos = [self numberOfPhotos]; 841 | 842 | // Setup pages 843 | [_visiblePages removeAllObjects]; 844 | [_recycledPages removeAllObjects]; 845 | 846 | // Toolbar 847 | if (_displayToolbar) { 848 | [self.view addSubview:_toolbar]; 849 | } else { 850 | [_toolbar removeFromSuperview]; 851 | } 852 | 853 | // Title 854 | if(_displayTitle && !self.navigationController.navigationBar) 855 | [self.view addSubview:_titleLabel]; 856 | 857 | // Close button 858 | if(_displayDoneButton && !self.navigationController.navigationBar) 859 | [self.view addSubview:_doneButton]; 860 | 861 | // Toolbar items & navigation 862 | UIBarButtonItem *fixedLeftSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace 863 | target:self action:nil]; 864 | fixedLeftSpace.width = 32; // To balance action button 865 | UIBarButtonItem *flexSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace 866 | target:self action:nil]; 867 | NSMutableArray *items = [NSMutableArray new]; 868 | 869 | if (_displayActionButton) 870 | [items addObject:fixedLeftSpace]; 871 | [items addObject:flexSpace]; 872 | 873 | if (numberOfPhotos > 1 && _displayArrowButton) 874 | [items addObject:_previousButton]; 875 | 876 | if(_displayCounterLabel) { 877 | [items addObject:flexSpace]; 878 | [items addObject:_counterButton]; 879 | } 880 | 881 | [items addObject:flexSpace]; 882 | if (numberOfPhotos > 1 && _displayArrowButton) 883 | [items addObject:_nextButton]; 884 | [items addObject:flexSpace]; 885 | 886 | if(_displayActionButton) 887 | [items addObject:_actionButton]; 888 | 889 | [_toolbar setItems:items]; 890 | [self updateToolbar]; 891 | 892 | // Content offset 893 | _pagingScrollView.contentOffset = [self contentOffsetForPageAtIndex:_currentPageIndex]; 894 | [self tilePages]; 895 | _performingLayout = NO; 896 | 897 | if(! _disableVerticalSwipe) { 898 | [self.view addGestureRecognizer:_panGesture]; 899 | } 900 | 901 | //Custom Backgroud 902 | if (_customBackgroud) { 903 | [self.view insertSubview:_customBackgroud atIndex:0]; 904 | } 905 | } 906 | 907 | #pragma mark - Data 908 | 909 | - (void)reloadData { 910 | // Get data 911 | [self releaseAllUnderlyingPhotos]; 912 | 913 | // Update 914 | [self performLayout]; 915 | 916 | // Layout 917 | [self.view setNeedsLayout]; 918 | } 919 | 920 | - (NSUInteger)numberOfPhotos { 921 | return _photos.count; 922 | } 923 | 924 | - (id)photoAtIndex:(NSUInteger)index { 925 | return _photos[index]; 926 | } 927 | 928 | - (ZYQCaptionView *)captionViewForPhotoAtIndex:(NSUInteger)index { 929 | ZYQCaptionView *captionView = nil; 930 | if ([_delegate respondsToSelector:@selector(photoBrowser:captionViewForPhotoAtIndex:)]) { 931 | captionView = [_delegate photoBrowser:self captionViewForPhotoAtIndex:index]; 932 | } else { 933 | id photo = [self photoAtIndex:index]; 934 | if ([photo respondsToSelector:@selector(caption)]) { 935 | if ([photo caption]) captionView = [[ZYQCaptionView alloc] initWithPhoto:photo]; 936 | } 937 | } 938 | captionView.alpha = [self areControlsHidden] ? 0 : 1; // Initial alpha 939 | 940 | return captionView; 941 | } 942 | 943 | - (UIImage *)imageForPhoto:(id)photo { 944 | if (photo) { 945 | // Get image or obtain in background 946 | if ([photo underlyingImage]) { 947 | return [photo underlyingImage]; 948 | } else { 949 | [photo loadUnderlyingImageAndNotify]; 950 | if ([photo respondsToSelector:@selector(placeholderImage)]) { 951 | return [photo placeholderImage]; 952 | } 953 | } 954 | } 955 | 956 | return nil; 957 | } 958 | 959 | - (void)loadAdjacentPhotosIfNecessary:(id)photo { 960 | ZYQZoomingScrollView *page = [self pageDisplayingPhoto:photo]; 961 | if (page) { 962 | // If page is current page then initiate loading of previous and next pages 963 | NSUInteger pageIndex = PAGE_INDEX(page); 964 | if (_currentPageIndex == pageIndex) { 965 | if (pageIndex > 0) { 966 | // Preload index - 1 967 | id photo = [self photoAtIndex:pageIndex-1]; 968 | if (![photo underlyingImage]) { 969 | [photo loadUnderlyingImageAndNotify]; 970 | ZYQLog(@"Pre-loading image at index %i", pageIndex-1); 971 | } 972 | } 973 | if (pageIndex < [self numberOfPhotos] - 1) { 974 | // Preload index + 1 975 | id photo = [self photoAtIndex:pageIndex+1]; 976 | if (![photo underlyingImage]) { 977 | [photo loadUnderlyingImageAndNotify]; 978 | ZYQLog(@"Pre-loading image at index %i", pageIndex+1); 979 | } 980 | } 981 | } 982 | } 983 | } 984 | 985 | #pragma mark - ZYQPhoto Loading Notification 986 | 987 | - (void)handleZYQPhotoLoadingDidEndNotification:(NSNotification *)notification { 988 | id photo = [notification object]; 989 | ZYQZoomingScrollView *page = [self pageDisplayingPhoto:photo]; 990 | if (page) { 991 | if ([photo underlyingImage]) { 992 | // Successful load 993 | [page displayImage]; 994 | [self loadAdjacentPhotosIfNecessary:photo]; 995 | } else { 996 | // Failed to load 997 | [page displayImageFailure]; 998 | if ([_delegate respondsToSelector:@selector(photoBrowser:imageFailed:imageView:)]) { 999 | NSUInteger pageIndex = PAGE_INDEX(page); 1000 | [_delegate photoBrowser:self imageFailed:pageIndex imageView:page.photoImageView]; 1001 | } 1002 | // make sure the page is completely updated 1003 | [page setNeedsLayout]; 1004 | } 1005 | } 1006 | } 1007 | 1008 | #pragma mark - Paging 1009 | 1010 | - (void)tilePages { 1011 | // Calculate which pages should be visible 1012 | // Ignore padding as paging bounces encroach on that 1013 | // and lead to false page loads 1014 | CGRect visibleBounds = _pagingScrollView.bounds; 1015 | NSInteger iFirstIndex = (NSInteger) floorf((CGRectGetMinX(visibleBounds)+PADDING*2) / CGRectGetWidth(visibleBounds)); 1016 | NSInteger iLastIndex = (NSInteger) floorf((CGRectGetMaxX(visibleBounds)-PADDING*2-1) / CGRectGetWidth(visibleBounds)); 1017 | if (iFirstIndex < 0) iFirstIndex = 0; 1018 | if (iFirstIndex > [self numberOfPhotos] - 1) iFirstIndex = [self numberOfPhotos] - 1; 1019 | if (iLastIndex < 0) iLastIndex = 0; 1020 | if (iLastIndex > [self numberOfPhotos] - 1) iLastIndex = [self numberOfPhotos] - 1; 1021 | 1022 | // Recycle no longer needed pages 1023 | NSInteger pageIndex; 1024 | for (ZYQZoomingScrollView *page in _visiblePages) { 1025 | pageIndex = PAGE_INDEX(page); 1026 | if (pageIndex < (NSUInteger)iFirstIndex || pageIndex > (NSUInteger)iLastIndex) { 1027 | [_recycledPages addObject:page]; 1028 | [page prepareForReuse]; 1029 | [page removeFromSuperview]; 1030 | ZYQLog(@"Removed page at index %i", PAGE_INDEX(page)); 1031 | } 1032 | } 1033 | [_visiblePages minusSet:_recycledPages]; 1034 | while (_recycledPages.count > 2) // Only keep 2 recycled pages 1035 | [_recycledPages removeObject:[_recycledPages anyObject]]; 1036 | 1037 | // Add missing pages 1038 | for (NSUInteger index = (NSUInteger)iFirstIndex; index <= (NSUInteger)iLastIndex; index++) { 1039 | if (![self isDisplayingPageForIndex:index]) { 1040 | // Add new page 1041 | ZYQZoomingScrollView *page; 1042 | page = [[ZYQZoomingScrollView alloc] initWithPhotoBrowser:self]; 1043 | page.backgroundColor = [UIColor clearColor]; 1044 | page.opaque = YES; 1045 | 1046 | [self configurePage:page forIndex:index]; 1047 | [_visiblePages addObject:page]; 1048 | [_pagingScrollView addSubview:page]; 1049 | ZYQLog(@"Added page at index %i", index); 1050 | 1051 | // Add caption 1052 | ZYQCaptionView *captionView = [self captionViewForPhotoAtIndex:index]; 1053 | captionView.frame = [self frameForCaptionView:captionView atIndex:index]; 1054 | [_pagingScrollView addSubview:captionView]; 1055 | page.captionView = captionView; 1056 | } 1057 | } 1058 | } 1059 | 1060 | - (BOOL)isDisplayingPageForIndex:(NSUInteger)index { 1061 | for (ZYQZoomingScrollView *page in _visiblePages) 1062 | if (PAGE_INDEX(page) == index) return YES; 1063 | return NO; 1064 | } 1065 | 1066 | - (ZYQZoomingScrollView *)pageDisplayedAtIndex:(NSUInteger)index { 1067 | ZYQZoomingScrollView *thePage = nil; 1068 | for (ZYQZoomingScrollView *page in _visiblePages) { 1069 | if (PAGE_INDEX(page) == index) { 1070 | thePage = page; break; 1071 | } 1072 | } 1073 | return thePage; 1074 | } 1075 | 1076 | - (ZYQZoomingScrollView *)pageDisplayingPhoto:(id)photo { 1077 | ZYQZoomingScrollView *thePage = nil; 1078 | for (ZYQZoomingScrollView *page in _visiblePages) { 1079 | if (page.photo == photo) { 1080 | thePage = page; break; 1081 | } 1082 | } 1083 | return thePage; 1084 | } 1085 | 1086 | - (void)configurePage:(ZYQZoomingScrollView *)page forIndex:(NSUInteger)index { 1087 | page.frame = [self frameForPageAtIndex:index]; 1088 | page.tag = PAGE_INDEX_TAG_OFFSET + index; 1089 | page.photo = [self photoAtIndex:index]; 1090 | 1091 | __block __weak ZYQPhoto *photo = (ZYQPhoto*)page.photo; 1092 | __weak ZYQZoomingScrollView* weakPage = page; 1093 | photo.progressUpdateBlock = ^(CGFloat progress){ 1094 | [weakPage setProgress:progress forPhoto:photo]; 1095 | }; 1096 | } 1097 | 1098 | - (ZYQZoomingScrollView *)dequeueRecycledPage { 1099 | ZYQZoomingScrollView *page = [_recycledPages anyObject]; 1100 | if (page) { 1101 | [_recycledPages removeObject:page]; 1102 | } 1103 | return page; 1104 | } 1105 | 1106 | // Handle page changes 1107 | - (void)didStartViewingPageAtIndex:(NSUInteger)index { 1108 | // Load adjacent images if needed and the photo is already 1109 | // loaded. Also called after photo has been loaded in background 1110 | id currentPhoto = [self photoAtIndex:index]; 1111 | if ([currentPhoto underlyingImage]) { 1112 | // photo loaded so load ajacent now 1113 | [self loadAdjacentPhotosIfNecessary:currentPhoto]; 1114 | } 1115 | if ([_delegate respondsToSelector:@selector(photoBrowser:didShowPhotoAtIndex:)]) { 1116 | [_delegate photoBrowser:self didShowPhotoAtIndex:index]; 1117 | } 1118 | } 1119 | 1120 | #pragma mark - Frame Calculations 1121 | 1122 | - (CGRect)frameForPagingScrollView { 1123 | CGRect frame = self.view.bounds; 1124 | frame.origin.x -= PADDING; 1125 | frame.size.width += (2 * PADDING); 1126 | return frame; 1127 | } 1128 | 1129 | - (CGRect)frameForPageAtIndex:(NSUInteger)index { 1130 | // We have to use our paging scroll view's bounds, not frame, to calculate the page placement. When the device is in 1131 | // landscape orientation, the frame will still be in portrait because the pagingScrollView is the root view controller's 1132 | // view, so its frame is in window coordinate space, which is never rotated. Its bounds, however, will be in landscape 1133 | // because it has a rotation transform applied. 1134 | CGRect bounds = _pagingScrollView.bounds; 1135 | CGRect pageFrame = bounds; 1136 | pageFrame.size.width -= (2 * PADDING); 1137 | pageFrame.origin.x = (bounds.size.width * index) + PADDING; 1138 | return pageFrame; 1139 | } 1140 | 1141 | - (CGSize)contentSizeForPagingScrollView { 1142 | // We have to use the paging scroll view's bounds to calculate the contentSize, for the same reason outlined above. 1143 | CGRect bounds = _pagingScrollView.bounds; 1144 | return CGSizeMake(bounds.size.width * [self numberOfPhotos], bounds.size.height); 1145 | } 1146 | 1147 | - (CGPoint)contentOffsetForPageAtIndex:(NSUInteger)index { 1148 | CGFloat pageWidth = _pagingScrollView.bounds.size.width; 1149 | CGFloat newOffset = index * pageWidth; 1150 | return CGPointMake(newOffset, 0); 1151 | } 1152 | 1153 | - (BOOL)isLandscape:(UIInterfaceOrientation)orientation 1154 | { 1155 | return UIInterfaceOrientationIsLandscape(orientation); 1156 | } 1157 | 1158 | - (CGRect)frameForToolbarAtOrientation:(UIInterfaceOrientation)orientation { 1159 | CGFloat height = 44; 1160 | 1161 | if ([self isLandscape:orientation]) 1162 | height = 32; 1163 | 1164 | return CGRectMake(0, self.view.bounds.size.height - height, self.view.bounds.size.width, height); 1165 | } 1166 | 1167 | - (CGRect)frameForDoneButtonAtOrientation:(UIInterfaceOrientation)orientation { 1168 | CGRect screenBound = self.view.bounds; 1169 | CGFloat screenWidth = screenBound.size.width; 1170 | 1171 | // if ([self isLandscape:orientation]) screenWidth = screenBound.size.height; 1172 | 1173 | return CGRectMake(screenWidth - self.doneButtonRightInset - self.doneButtonSize.width, self.doneButtonTopInset, self.doneButtonSize.width, self.doneButtonSize.height); 1174 | } 1175 | 1176 | - (CGRect)frameForTitleLabelAtOrientation:(UIInterfaceOrientation)orientation { 1177 | CGRect screenBound = self.view.bounds; 1178 | CGFloat screenWidth = screenBound.size.width; 1179 | CGFloat statusBarBottom=[UIApplication sharedApplication].statusBarFrame.origin.y+[UIApplication sharedApplication].statusBarFrame.size.height; 1180 | 1181 | // if ([self isLandscape:orientation]) screenWidth = screenBound.size.height; 1182 | 1183 | return CGRectMake(0, statusBarBottom, screenWidth, 44); 1184 | } 1185 | 1186 | - (CGRect)frameForCaptionView:(ZYQCaptionView *)captionView atIndex:(NSUInteger)index { 1187 | CGRect pageFrame = [self frameForPageAtIndex:index]; 1188 | 1189 | CGSize captionSize = [captionView sizeThatFits:CGSizeMake(pageFrame.size.width, 0)]; 1190 | CGRect captionFrame = CGRectMake(pageFrame.origin.x, pageFrame.size.height - captionSize.height - (_toolbar.superview?_toolbar.frame.size.height:0), pageFrame.size.width, captionSize.height); 1191 | 1192 | return captionFrame; 1193 | } 1194 | 1195 | #pragma mark - UIScrollView Delegate 1196 | 1197 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView { 1198 | // Checks 1199 | if (!_viewIsActive || _performingLayout || _rotating) return; 1200 | 1201 | // Tile pages 1202 | [self tilePages]; 1203 | 1204 | // Calculate current page 1205 | CGRect visibleBounds = _pagingScrollView.bounds; 1206 | NSInteger index = (NSInteger) (floorf(CGRectGetMidX(visibleBounds) / CGRectGetWidth(visibleBounds))); 1207 | if (index < 0) index = 0; 1208 | if (index > [self numberOfPhotos] - 1) index = [self numberOfPhotos] - 1; 1209 | NSUInteger previousCurrentPage = _currentPageIndex; 1210 | _currentPageIndex = index; 1211 | if (_currentPageIndex != previousCurrentPage) { 1212 | [self didStartViewingPageAtIndex:index]; 1213 | 1214 | if(_arrowButtonsChangePhotosAnimated) [self updateToolbar]; 1215 | } 1216 | } 1217 | 1218 | - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { 1219 | // Hide controls when dragging begins 1220 | if(_autoHideInterface){ 1221 | [self setControlsHidden:YES animated:YES permanent:NO]; 1222 | } 1223 | } 1224 | 1225 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { 1226 | // Update toolbar when page changes 1227 | if(! _arrowButtonsChangePhotosAnimated) [self updateToolbar]; 1228 | } 1229 | 1230 | #pragma mark - Toolbar 1231 | 1232 | - (void)updateToolbar { 1233 | //Title 1234 | if ([_delegate respondsToSelector:@selector(photoBrowser:titleForPhotoAtIndex:)]) { 1235 | _titleLabel.text=[_delegate photoBrowser:self titleForPhotoAtIndex:_currentPageIndex]; 1236 | } 1237 | else{ 1238 | _titleLabel.text=ZYQPhotoBrowserLocalizedStrings(@"PhotoBrowser"); 1239 | } 1240 | 1241 | // Counter 1242 | if ([_delegate respondsToSelector:@selector(photoBrowser:counterForPhotoAtIndex:)]) { 1243 | _counterLabel.text=[_delegate photoBrowser:self counterForPhotoAtIndex:_currentPageIndex]; 1244 | } 1245 | else{ 1246 | if ([self numberOfPhotos] > 1) { 1247 | _counterLabel.text = [NSString stringWithFormat:@"%lu %@ %lu", (unsigned long)(_currentPageIndex+1), ZYQPhotoBrowserLocalizedStrings(@"of"), (unsigned long)[self numberOfPhotos]]; 1248 | } else { 1249 | _counterLabel.text = nil; 1250 | } 1251 | } 1252 | 1253 | // Buttons 1254 | _previousButton.enabled = (_currentPageIndex > 0); 1255 | _nextButton.enabled = (_currentPageIndex < [self numberOfPhotos]-1); 1256 | } 1257 | 1258 | - (void)jumpToPageAtIndex:(NSUInteger)index { 1259 | // Change page 1260 | if (index < [self numberOfPhotos]) { 1261 | CGRect pageFrame = [self frameForPageAtIndex:index]; 1262 | 1263 | if(_arrowButtonsChangePhotosAnimated) 1264 | { 1265 | [_pagingScrollView setContentOffset:CGPointMake(pageFrame.origin.x - PADDING, 0) animated:YES]; 1266 | } 1267 | else 1268 | { 1269 | _pagingScrollView.contentOffset = CGPointMake(pageFrame.origin.x - PADDING, 0); 1270 | [self updateToolbar]; 1271 | } 1272 | } 1273 | 1274 | // Update timer to give more time 1275 | [self hideControlsAfterDelay]; 1276 | } 1277 | 1278 | - (void)gotoPreviousPage { [self jumpToPageAtIndex:_currentPageIndex-1]; } 1279 | - (void)gotoNextPage { [self jumpToPageAtIndex:_currentPageIndex+1]; } 1280 | 1281 | #pragma mark - Control Hiding / Showing 1282 | 1283 | // If permanent then we don't set timers to hide again 1284 | - (void)setControlsHidden:(BOOL)hidden animated:(BOOL)animated permanent:(BOOL)permanent { 1285 | // Cancel any timers 1286 | [self cancelControlHiding]; 1287 | 1288 | // Captions 1289 | NSMutableSet *captionViews = [[NSMutableSet alloc] initWithCapacity:_visiblePages.count]; 1290 | for (ZYQZoomingScrollView *page in _visiblePages) { 1291 | if (page.captionView) [captionViews addObject:page.captionView]; 1292 | } 1293 | 1294 | // Hide/show bars 1295 | [UIView animateWithDuration:(animated ? 0.1 : 0) animations:^(void) { 1296 | CGFloat alpha = hidden ? 0 : 1; 1297 | [self.navigationController.navigationBar setAlpha:alpha]; 1298 | [_toolbar setAlpha:alpha]; 1299 | [_doneButton setAlpha:alpha]; 1300 | [_titleLabel setAlpha:alpha]; 1301 | for (UIView *v in captionViews) v.alpha = alpha; 1302 | } completion:^(BOOL finished) {}]; 1303 | 1304 | // Control hiding timer 1305 | // Will cancel existing timer but only begin hiding if they are visible 1306 | if (!permanent) { 1307 | [self hideControlsAfterDelay]; 1308 | } 1309 | 1310 | [self setNeedsStatusBarAppearanceUpdate]; 1311 | } 1312 | 1313 | - (void)cancelControlHiding { 1314 | // If a timer exists then cancel and release 1315 | if (_controlVisibilityTimer) { 1316 | [_controlVisibilityTimer invalidate]; 1317 | _controlVisibilityTimer = nil; 1318 | } 1319 | } 1320 | 1321 | // Enable/disable control visiblity timer 1322 | - (void)hideControlsAfterDelay { 1323 | if (![self autoHideInterface]) { 1324 | return; 1325 | } 1326 | 1327 | if (![self areControlsHidden]) { 1328 | [self cancelControlHiding]; 1329 | _controlVisibilityTimer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(hideControls) userInfo:nil repeats:NO]; 1330 | } 1331 | } 1332 | 1333 | - (BOOL)areControlsHidden { 1334 | return (_toolbar.alpha == 0); 1335 | } 1336 | 1337 | - (void)hideControls { 1338 | if(_autoHide && _autoHideInterface) { 1339 | [self setControlsHidden:YES animated:YES permanent:NO]; 1340 | } 1341 | } 1342 | - (void)handleSingleTap { 1343 | if (_dismissOnTouch) { 1344 | [self doneButtonPressed:nil]; 1345 | } else { 1346 | [self setControlsHidden:![self areControlsHidden] animated:YES permanent:NO]; 1347 | } 1348 | } 1349 | 1350 | 1351 | #pragma mark - Properties 1352 | 1353 | -(NSArray *)photos{ 1354 | return _photos; 1355 | } 1356 | 1357 | - (void)setInitialPageIndex:(NSUInteger)index { 1358 | // Validate 1359 | if (index >= [self numberOfPhotos]) index = [self numberOfPhotos]-1; 1360 | _initalPageIndex = index; 1361 | _currentPageIndex = index; 1362 | if ([self isViewLoaded]) { 1363 | [self jumpToPageAtIndex:index]; 1364 | if (!_viewIsActive) [self tilePages]; // Force tiling if view is not visible 1365 | } 1366 | } 1367 | 1368 | - (void)setGifSupportImageViewClass:(Class)gifSupportImageViewClass{ 1369 | if (_gifSupportImageViewClass==gifSupportImageViewClass) { 1370 | return; 1371 | } 1372 | _gifSupportImageViewClass=gifSupportImageViewClass; 1373 | 1374 | [self zyq_hookOrAddWithOriginSeletor:@selector(touchesEnded:withEvent:) originClass:_gifSupportImageViewClass swizzledSelector:@selector(touchesEnded:withEvent:) swizzledClass:NSClassFromString(@"ZYQTapDetectingImageView")]; 1375 | [self zyq_hookOrAddWithOriginSeletor:@selector(handleSingleTap:) originClass:_gifSupportImageViewClass swizzledSelector:@selector(handleSingleTap:) swizzledClass:NSClassFromString(@"ZYQTapDetectingImageView")]; 1376 | [self zyq_hookOrAddWithOriginSeletor:@selector(handleDoubleTap:) originClass:_gifSupportImageViewClass swizzledSelector:@selector(handleDoubleTap:) swizzledClass:NSClassFromString(@"ZYQTapDetectingImageView")]; 1377 | [self zyq_hookOrAddWithOriginSeletor:@selector(handleTripleTap:) originClass:_gifSupportImageViewClass swizzledSelector:@selector(handleTripleTap:) swizzledClass:NSClassFromString(@"ZYQTapDetectingImageView")]; 1378 | [self zyq_hookOrAddWithOriginSeletor:@selector(setTapDelegate:) originClass:_gifSupportImageViewClass swizzledSelector:@selector(setTapDelegate:) swizzledClass:NSClassFromString(@"ZYQTapDetectingImageView")]; 1379 | [self zyq_hookOrAddWithOriginSeletor:@selector(tapDelegate) originClass:_gifSupportImageViewClass swizzledSelector:@selector(tapDelegate) swizzledClass:NSClassFromString(@"ZYQTapDetectingImageView")]; 1380 | 1381 | 1382 | } 1383 | 1384 | -(void)zyq_hookOrAddWithOriginSeletor:(SEL)originalSelector originClass:(Class)originClass swizzledSelector:(SEL)swizzledSelector swizzledClass:(Class)swizzledClass{ 1385 | 1386 | Method originalMethod = class_getInstanceMethod(originClass, originalSelector); 1387 | Method swizzledMethod = class_getInstanceMethod(swizzledClass, swizzledSelector); 1388 | 1389 | static OSSpinLock aspect_lock = OS_SPINLOCK_INIT; 1390 | OSSpinLockLock(&aspect_lock); 1391 | 1392 | BOOL success = class_addMethod(originClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); 1393 | if (success) { 1394 | class_replaceMethod(originClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); 1395 | } else { 1396 | method_exchangeImplementations(originalMethod, swizzledMethod); 1397 | } 1398 | OSSpinLockUnlock(&aspect_lock); 1399 | 1400 | } 1401 | 1402 | 1403 | #pragma mark - Buttons 1404 | 1405 | - (void)doneButtonPressed:(id)sender { 1406 | if ([_delegate respondsToSelector:@selector(willDisappearPhotoBrowser:)]) { 1407 | [_delegate willDisappearPhotoBrowser:self]; 1408 | } 1409 | 1410 | if (_senderViewForAnimation && _currentPageIndex == _initalPageIndex) { 1411 | ZYQZoomingScrollView *scrollView = [self pageDisplayedAtIndex:_currentPageIndex]; 1412 | [self performCloseAnimationWithScrollView:scrollView]; 1413 | } 1414 | else { 1415 | _senderViewForAnimation.hidden = NO; 1416 | [self prepareForClosePhotoBrowser]; 1417 | [self dismissPhotoBrowserAnimated:YES]; 1418 | } 1419 | } 1420 | 1421 | - (void)actionButtonPressed:(id)sender { 1422 | id photo = [self photoAtIndex:_currentPageIndex]; 1423 | 1424 | if ([self numberOfPhotos] > 0 && [photo underlyingImage]) { 1425 | if(!_actionButtonTitles) 1426 | { 1427 | // Activity view 1428 | NSMutableArray *activityItems = [NSMutableArray arrayWithObject:[photo underlyingImage]]; 1429 | if (photo.caption) [activityItems addObject:photo.caption]; 1430 | 1431 | self.activityViewController = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:nil]; 1432 | 1433 | __typeof__(self) __weak selfBlock = self; 1434 | 1435 | [self.activityViewController setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { 1436 | [selfBlock hideControlsAfterDelay]; 1437 | selfBlock.activityViewController = nil; 1438 | }]; 1439 | 1440 | if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { 1441 | [self presentViewController:self.activityViewController animated:YES completion:nil]; 1442 | } 1443 | else { // iPad 1444 | #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 1445 | 1446 | self.activityViewController.modalPresentationStyle = UIModalPresentationPopover; 1447 | self.activityViewController.preferredContentSize = CGSizeMake(300, 400); 1448 | 1449 | UIPopoverPresentationController *popover = self.activityViewController.popoverPresentationController; 1450 | [popover setSourceRect:CGRectMake(self.view.frame.size.width/2, self.view.frame.size.height/4, 0, 0)]; 1451 | [popover setSourceView:self.view]; 1452 | [self presentViewController:self.activityViewController animated:YES completion:nil]; 1453 | #else 1454 | UIPopoverController *popover = [[UIPopoverController alloc] initWithContentViewController:self.activityViewController]; 1455 | [popover presentPopoverFromRect:CGRectMake(self.view.frame.size.width/2, self.view.frame.size.height/4, 0, 0) 1456 | inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny 1457 | animated:YES]; 1458 | #endif 1459 | } 1460 | } 1461 | else 1462 | { 1463 | // Action sheet 1464 | #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 1465 | self.actionsSheet=[UIAlertController alertControllerWithTitle:@"" message:@"" preferredStyle:UIAlertControllerStyleActionSheet]; 1466 | for (NSInteger i=0; i<_actionButtonTitles.count; i++) { 1467 | UIAlertAction *alertAction=[UIAlertAction actionWithTitle:_actionButtonTitles[i] style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 1468 | if ([_delegate respondsToSelector:@selector(photoBrowser:didDismissActionSheetWithButtonIndex:photoIndex:)]) { 1469 | [_delegate photoBrowser:self didDismissActionSheetWithButtonIndex:i photoIndex:_currentPageIndex]; 1470 | return; 1471 | } 1472 | }]; 1473 | [self.actionsSheet addAction:alertAction]; 1474 | } 1475 | 1476 | UIAlertAction *cancelAlertAction=[UIAlertAction actionWithTitle:ZYQPhotoBrowserLocalizedStrings(@"Cancel") style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { 1477 | [self hideControlsAfterDelay]; 1478 | }]; 1479 | 1480 | [self.actionsSheet addAction:cancelAlertAction]; 1481 | 1482 | 1483 | [self presentViewController:self.actionsSheet animated:YES completion:nil]; 1484 | 1485 | #else 1486 | self.actionsSheet = [UIActionSheet new]; 1487 | self.actionsSheet.delegate = self; 1488 | for(NSString *action in _actionButtonTitles) { 1489 | [self.actionsSheet addButtonWithTitle:action]; 1490 | } 1491 | 1492 | self.actionsSheet.cancelButtonIndex = [self.actionsSheet addButtonWithTitle:ZYQPhotoBrowserLocalizedStrings(@"Cancel")]; 1493 | self.actionsSheet.actionSheetStyle = UIActionSheetStyleBlackTranslucent; 1494 | 1495 | if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { 1496 | [_actionsSheet showInView:self.view]; 1497 | } else { 1498 | [_actionsSheet showFromBarButtonItem:sender animated:YES]; 1499 | } 1500 | #endif 1501 | 1502 | 1503 | } 1504 | 1505 | // Keep controls hidden 1506 | [self setControlsHidden:NO animated:YES permanent:YES]; 1507 | } 1508 | } 1509 | 1510 | #pragma mark - Action Sheet Delegate 1511 | 1512 | #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 1513 | #else 1514 | - (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex { 1515 | if (actionSheet == _actionsSheet) { 1516 | self.actionsSheet = nil; 1517 | 1518 | if (buttonIndex != actionSheet.cancelButtonIndex) { 1519 | if ([_delegate respondsToSelector:@selector(photoBrowser:didDismissActionSheetWithButtonIndex:photoIndex:)]) { 1520 | [_delegate photoBrowser:self didDismissActionSheetWithButtonIndex:buttonIndex photoIndex:_currentPageIndex]; 1521 | return; 1522 | } 1523 | } 1524 | } 1525 | 1526 | [self hideControlsAfterDelay]; // Continue as normal... 1527 | } 1528 | #endif 1529 | 1530 | 1531 | #pragma mark - Zoom Animation 1532 | 1533 | - (void)animateView:(UIView *)view toFrame:(CGRect)frame completion:(void (^)(void))completion 1534 | { 1535 | if ([self conformsToProtocol:objc_getProtocol("ZYQPhotoBrowserCustomProtocol")]) { 1536 | [(ZYQPhotoBrowser*)self animateView:view toFrame:frame completion:completion]; 1537 | } 1538 | else{ 1539 | [UIView animateWithDuration:0.5 animations:^{ 1540 | view.frame=frame; 1541 | } completion:^(BOOL finished) { 1542 | if (completion) { 1543 | completion(); 1544 | } 1545 | }]; 1546 | } 1547 | } 1548 | @end 1549 | -------------------------------------------------------------------------------- /ZYQPhotoBrowser/ZYQPhotoProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZYQPhotoProtocol.h 3 | // ZYQPhotoBrowser 4 | // 5 | // Created by Michael Waterfall on 02/01/2012. 6 | // Copyright (c) 2012 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "ZYQPBConstants.h" 11 | 12 | // Name of notification used when a photo has completed loading process 13 | // Used to notify browser display the image 14 | #define ZYQPhoto_LOADING_DID_END_NOTIFICATION @"ZYQPhoto_LOADING_DID_END_NOTIFICATION" 15 | 16 | //If you wish to use custom libraies for download image ... 17 | //you can use this protocol 18 | 19 | //ZYQPhotoBrowser's 20 | @protocol ZYQPhotoBrowserCustomProtocol 21 | 22 | @required 23 | - (void)custom_animateView:(UIView *)view toFrame:(CGRect)frame completion:(void (^)(void))completion; 24 | 25 | @end 26 | 27 | //ZYQPhoto's 28 | @protocol ZYQPhotoCustomLoadProtocol 29 | 30 | @required 31 | -(void)loadImageWithURL:(NSURL*)url updateProgressBlock:(void (^)(CGFloat progress))updateProgressBlock completedBlock:(void (^)(UIImage *image,NSError *error))completedBlock; 32 | -(id)loadImageWithFile:(NSString *)path; 33 | 34 | @end 35 | 36 | //ZYQZoomingScrollView's 37 | @class ZYQPhotoBrowser; 38 | @protocol ZYQZoomingScrollViewCustomProtocol 39 | 40 | @required 41 | -(id)getCustomProgressViewWithBrowser:(ZYQPhotoBrowser*)browser; 42 | 43 | @end 44 | 45 | //Your ProgressView's 46 | @protocol ZYQProgressViewProtocol 47 | 48 | @required 49 | @property(nonatomic,assign)CGFloat zyq_progress; 50 | 51 | @end 52 | 53 | 54 | // If you wish to use your own data models for photo then they must conform 55 | // to this protocol. See instructions for details on each method. 56 | // Otherwise you can use the ZYQPhoto object or subclass it yourself to 57 | // store more information per photo. 58 | // 59 | // You can see the ZYQPhoto class for an example implementation of this protocol 60 | // 61 | @protocol ZYQPhoto 62 | 63 | @required 64 | 65 | // Return underlying UIImage to be displayed 66 | // Return nil if the image is not immediately available (loaded into memory, preferably 67 | // already decompressed) and needs to be loaded from a source (cache, file, web, etc) 68 | // IMPORTANT: You should *NOT* use this method to initiate 69 | // fetching of images from any external of source. That should be handled 70 | // in -loadUnderlyingImageAndNotify: which may be called by the photo browser if this 71 | // methods returns nil. 72 | - (UIImage *)underlyingImage; 73 | 74 | // Called when the browser has determined the underlying images is not 75 | // already loaded into memory but needs it. 76 | // You must load the image asyncronously (and decompress it for better performance). 77 | // See ZYQPhoto object for an example implementation. 78 | // When the underlying UIImage is loaded (or failed to load) you should post the following 79 | // notification: 80 | // 81 | // [[NSNotificationCenter defaultCenter] postNotificationName:ZYQPhoto_LOADING_DID_END_NOTIFICATION 82 | // object:self]; 83 | // 84 | - (void)loadUnderlyingImageAndNotify; 85 | 86 | // This is called when the photo browser has determined the photo data 87 | // is no longer needed or there are low memory conditions 88 | // You should release any underlying (possibly large and decompressed) image data 89 | // as long as the image can be re-loaded (from cache, file, or URL) 90 | - (void)unloadUnderlyingImage; 91 | 92 | @optional 93 | 94 | // Return a caption string to be displayed over the image 95 | // Return nil to display no caption 96 | - (NSString *)caption; 97 | 98 | // Return placeholder UIImage to be displayed while loading underlyingImage 99 | // Return nil if there is no placeholder 100 | - (UIImage *)placeholderImage; 101 | 102 | @end 103 | -------------------------------------------------------------------------------- /ZYQPhotoBrowser/ZYQTapDetectingImageView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZYQTapDetectingImageView.h 3 | // ZYQPhotoBrowser 4 | // 5 | // Created by Michael Waterfall on 04/11/2009. 6 | // Copyright 2009 d3i. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol ZYQTapDetectingImageViewDelegate; 12 | #ifdef NODEPENDENCY 13 | @interface ZYQTapDetectingImageView : UIImageView 14 | #else 15 | #import "FLAnimatedImageView.h" 16 | @interface ZYQTapDetectingImageView : FLAnimatedImageView 17 | #endif 18 | 19 | @property (nonatomic, weak) id tapDelegate; 20 | - (void)handleSingleTap:(UITouch *)touch; 21 | - (void)handleDoubleTap:(UITouch *)touch; 22 | - (void)handleTripleTap:(UITouch *)touch; 23 | @end 24 | 25 | @protocol ZYQTapDetectingImageViewDelegate 26 | @optional 27 | - (void)imageView:(UIImageView *)imageView singleTapDetected:(UITouch *)touch; 28 | - (void)imageView:(UIImageView *)imageView doubleTapDetected:(UITouch *)touch; 29 | - (void)imageView:(UIImageView *)imageView tripleTapDetected:(UITouch *)touch; 30 | @end 31 | -------------------------------------------------------------------------------- /ZYQPhotoBrowser/ZYQTapDetectingImageView.m: -------------------------------------------------------------------------------- 1 | // 2 | // ZYQTapDetectingImageView.m 3 | // ZYQPhotoBrowser 4 | // 5 | // Created by Michael Waterfall on 04/11/2009. 6 | // Copyright 2009 d3i. All rights reserved. 7 | // 8 | 9 | #import "ZYQTapDetectingImageView.h" 10 | #import 11 | 12 | #ifdef NODEPENDENCY 13 | #else 14 | #import "UIImage+GIF.h" 15 | #import "FLAnimatedImage.h" 16 | #endif 17 | 18 | @implementation ZYQTapDetectingImageView 19 | 20 | #ifdef NODEPENDENCY 21 | #else 22 | -(void)setImage:(UIImage *)image{ 23 | if (![image isKindOfClass:[UIImage class]]||image.isGIF) { 24 | NSLog(@"Gif"); 25 | self.animatedImage=(FLAnimatedImage*)image; 26 | } 27 | else{ 28 | [super setImage:image]; 29 | } 30 | } 31 | #endif 32 | 33 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { 34 | UITouch *touch = [touches anyObject]; 35 | NSUInteger tapCount = touch.tapCount; 36 | switch (tapCount) { 37 | case 1: 38 | [self handleSingleTap:touch]; 39 | break; 40 | case 2: 41 | [self handleDoubleTap:touch]; 42 | break; 43 | case 3: 44 | [self handleTripleTap:touch]; 45 | break; 46 | default: 47 | break; 48 | } 49 | [[self nextResponder] touchesEnded:touches withEvent:event]; 50 | } 51 | 52 | - (void)handleSingleTap:(UITouch *)touch { 53 | if ([self.tapDelegate respondsToSelector:@selector(imageView:singleTapDetected:)]) 54 | [self.tapDelegate imageView:self singleTapDetected:touch]; 55 | } 56 | 57 | - (void)handleDoubleTap:(UITouch *)touch { 58 | if ([self.tapDelegate respondsToSelector:@selector(imageView:doubleTapDetected:)]) 59 | [self.tapDelegate imageView:self doubleTapDetected:touch]; 60 | } 61 | 62 | - (void)handleTripleTap:(UITouch *)touch { 63 | if ([self.tapDelegate respondsToSelector:@selector(imageView:tripleTapDetected:)]) 64 | [self.tapDelegate imageView:self tripleTapDetected:touch]; 65 | } 66 | 67 | -(void)setTapDelegate:(id)tapDelegate{ 68 | objc_setAssociatedObject(self, @selector(tapDelegate), tapDelegate, OBJC_ASSOCIATION_ASSIGN); 69 | 70 | self.userInteractionEnabled=YES; 71 | } 72 | 73 | -(id)tapDelegate{ 74 | return objc_getAssociatedObject(self, @selector(tapDelegate)); 75 | } 76 | 77 | @end 78 | -------------------------------------------------------------------------------- /ZYQPhotoBrowser/ZYQTapDetectingView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZYQTapDetectingView.h 3 | // ZYQPhotoBrowser 4 | // 5 | // Created by Michael Waterfall on 04/11/2009. 6 | // Copyright 2009 d3i. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol ZYQTapDetectingViewDelegate; 12 | 13 | @interface ZYQTapDetectingView : UIView { 14 | id __weak tapDelegate; 15 | } 16 | @property (nonatomic, weak) id tapDelegate; 17 | - (void)handleSingleTap:(UITouch *)touch; 18 | - (void)handleDoubleTap:(UITouch *)touch; 19 | - (void)handleTripleTap:(UITouch *)touch; 20 | @end 21 | 22 | @protocol ZYQTapDetectingViewDelegate 23 | @optional 24 | - (void)view:(UIView *)view singleTapDetected:(UITouch *)touch; 25 | - (void)view:(UIView *)view doubleTapDetected:(UITouch *)touch; 26 | - (void)view:(UIView *)view tripleTapDetected:(UITouch *)touch; 27 | @end 28 | -------------------------------------------------------------------------------- /ZYQPhotoBrowser/ZYQTapDetectingView.m: -------------------------------------------------------------------------------- 1 | // 2 | // ZYQTapDetectingView.m 3 | // ZYQPhotoBrowser 4 | // 5 | // Created by Michael Waterfall on 04/11/2009. 6 | // Copyright 2009 d3i. All rights reserved. 7 | // 8 | 9 | #import "ZYQTapDetectingView.h" 10 | 11 | @implementation ZYQTapDetectingView 12 | 13 | @synthesize tapDelegate; 14 | 15 | - (id)init { 16 | if ((self = [super init])) { 17 | self.userInteractionEnabled = YES; 18 | } 19 | return self; 20 | } 21 | 22 | - (id)initWithFrame:(CGRect)frame { 23 | if ((self = [super initWithFrame:frame])) { 24 | self.userInteractionEnabled = YES; 25 | } 26 | return self; 27 | } 28 | 29 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { 30 | UITouch *touch = [touches anyObject]; 31 | NSUInteger tapCount = touch.tapCount; 32 | switch (tapCount) { 33 | case 1: 34 | [self handleSingleTap:touch]; 35 | break; 36 | case 2: 37 | [self handleDoubleTap:touch]; 38 | break; 39 | case 3: 40 | [self handleTripleTap:touch]; 41 | break; 42 | default: 43 | break; 44 | } 45 | [[self nextResponder] touchesEnded:touches withEvent:event]; 46 | } 47 | 48 | - (void)handleSingleTap:(UITouch *)touch { 49 | if ([tapDelegate respondsToSelector:@selector(view:singleTapDetected:)]) 50 | [tapDelegate view:self singleTapDetected:touch]; 51 | } 52 | 53 | - (void)handleDoubleTap:(UITouch *)touch { 54 | if ([tapDelegate respondsToSelector:@selector(view:doubleTapDetected:)]) 55 | [tapDelegate view:self doubleTapDetected:touch]; 56 | } 57 | 58 | - (void)handleTripleTap:(UITouch *)touch { 59 | if ([tapDelegate respondsToSelector:@selector(view:tripleTapDetected:)]) 60 | [tapDelegate view:self tripleTapDetected:touch]; 61 | } 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /ZYQPhotoBrowser/ZYQZoomingScrollView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZYQZoomingScrollView.h 3 | // ZYQPhotoBrowser 4 | // 5 | // Created by Michael Waterfall on 14/10/2010. 6 | // Copyright 2010 d3i. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "ZYQPhotoProtocol.h" 11 | #import "ZYQTapDetectingImageView.h" 12 | #import "ZYQTapDetectingView.h" 13 | 14 | #ifdef NODEPENDENCY 15 | #else 16 | #import 17 | #endif 18 | 19 | @class ZYQPhotoBrowser, ZYQPhoto, ZYQCaptionView; 20 | 21 | @interface ZYQZoomingScrollView : UIScrollView { 22 | 23 | ZYQPhotoBrowser *__weak _photoBrowser; 24 | id _photo; 25 | 26 | // This view references the related caption view for simplified handling in photo browser 27 | ZYQCaptionView *_captionView; 28 | 29 | ZYQTapDetectingView *_tapView; // for background taps 30 | 31 | UIView *_progressView; 32 | } 33 | 34 | @property (nonatomic, strong) ZYQTapDetectingImageView *photoImageView; 35 | @property (nonatomic, strong) ZYQCaptionView *captionView; 36 | @property (nonatomic, strong) id photo; 37 | @property (nonatomic) CGFloat maximumDoubleTapZoomScale; 38 | 39 | - (id)initWithPhotoBrowser:(ZYQPhotoBrowser *)browser; 40 | - (void)displayImage; 41 | - (void)displayImageFailure; 42 | - (void)setProgress:(CGFloat)progress forPhoto:(ZYQPhoto*)photo; 43 | - (void)setMaxMinZoomScalesForCurrentBounds; 44 | - (void)prepareForReuse; 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /ZYQPhotoBrowser/ZYQZoomingScrollView.m: -------------------------------------------------------------------------------- 1 | // 2 | // ZYQZoomingScrollView.m 3 | // ZYQPhotoBrowser 4 | // 5 | // Created by Michael Waterfall on 14/10/2010. 6 | // Copyright 2010 d3i. All rights reserved. 7 | // 8 | 9 | #import "ZYQZoomingScrollView.h" 10 | #import "ZYQPhotoBrowser.h" 11 | #import "ZYQPhoto.h" 12 | 13 | #import 14 | 15 | // Declare private methods of browser 16 | @interface ZYQPhotoBrowser () 17 | - (UIImage *)imageForPhoto:(id)photo; 18 | - (void)cancelControlHiding; 19 | - (void)hideControlsAfterDelay; 20 | //- (void)toggleControls; 21 | - (void)handleSingleTap; 22 | @end 23 | 24 | // Private methods and properties 25 | @interface ZYQZoomingScrollView () 26 | @property (nonatomic, weak) ZYQPhotoBrowser *photoBrowser; 27 | - (void)handleSingleTap:(CGPoint)touchPoint; 28 | - (void)handleDoubleTap:(CGPoint)touchPoint; 29 | @end 30 | 31 | @implementation ZYQZoomingScrollView 32 | 33 | @synthesize photoImageView = _photoImageView, photoBrowser = _photoBrowser, photo = _photo, captionView = _captionView; 34 | 35 | - (id)initWithPhotoBrowser:(ZYQPhotoBrowser *)browser { 36 | if ((self = [super init])) { 37 | // Delegate 38 | self.photoBrowser = browser; 39 | 40 | // Tap view for background 41 | _tapView = [[ZYQTapDetectingView alloc] initWithFrame:self.bounds]; 42 | _tapView.tapDelegate = self; 43 | _tapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 44 | _tapView.backgroundColor = [UIColor clearColor]; 45 | [self addSubview:_tapView]; 46 | 47 | // Image view 48 | Class myTapDetectingImageViewClass=[ZYQTapDetectingImageView class]; 49 | if (browser.gifSupportImageViewClass) { 50 | myTapDetectingImageViewClass=browser.gifSupportImageViewClass; 51 | } 52 | 53 | _photoImageView = [[myTapDetectingImageViewClass alloc] initWithFrame:CGRectZero]; 54 | _photoImageView.tapDelegate = self; 55 | _photoImageView.backgroundColor = [UIColor clearColor]; 56 | [self addSubview:_photoImageView]; 57 | 58 | CGRect screenBound = [[UIScreen mainScreen] bounds]; 59 | CGFloat screenWidth = screenBound.size.width; 60 | CGFloat screenHeight = screenBound.size.height; 61 | 62 | if ([[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationLandscapeLeft || [[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationLandscapeRight) { 63 | screenWidth = screenBound.size.height; 64 | screenHeight = screenBound.size.width; 65 | } 66 | 67 | // Progress view 68 | if ([self conformsToProtocol:objc_getProtocol("ZYQZoomingScrollViewCustomProtocol")]) { 69 | _progressView=[(ZYQZoomingScrollView*)self getCustomProgressViewWithBrowser:browser]; 70 | } 71 | else{ 72 | #ifdef NODEPENDENCY 73 | #else 74 | DACircularProgressView *tmpProgressView = [[DACircularProgressView alloc] initWithFrame:CGRectMake((screenWidth-35.)/2., (screenHeight-35.)/2, 35.0f, 35.0f)]; 75 | [tmpProgressView setProgress:0.0f]; 76 | tmpProgressView.tag = 101; 77 | tmpProgressView.thicknessRatio = 0.1; 78 | tmpProgressView.roundedCorners = NO; 79 | tmpProgressView.trackTintColor = browser.trackTintColor ? self.photoBrowser.trackTintColor : [UIColor colorWithWhite:0.2 alpha:1]; 80 | tmpProgressView.progressTintColor = browser.progressTintColor ? self.photoBrowser.progressTintColor : [UIColor colorWithWhite:1.0 alpha:1]; 81 | 82 | _progressView=tmpProgressView; 83 | #endif 84 | 85 | } 86 | [self addSubview:_progressView]; 87 | 88 | // Setup 89 | self.backgroundColor = [UIColor clearColor]; 90 | self.delegate = self; 91 | self.showsHorizontalScrollIndicator = NO; 92 | self.showsVerticalScrollIndicator = NO; 93 | self.decelerationRate = UIScrollViewDecelerationRateFast; 94 | self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 95 | } 96 | 97 | return self; 98 | } 99 | 100 | - (void)setPhoto:(id)photo { 101 | _photoImageView.image = nil; // Release image 102 | if (_photo != photo) { 103 | _photo = photo; 104 | } 105 | [self displayImage]; 106 | } 107 | 108 | - (void)prepareForReuse { 109 | self.photo = nil; 110 | [_captionView removeFromSuperview]; 111 | self.captionView = nil; 112 | } 113 | 114 | #pragma mark - Image 115 | 116 | // Get and display image 117 | - (void)displayImage { 118 | if (_photo) { 119 | // Reset 120 | self.maximumZoomScale = 1; 121 | self.minimumZoomScale = 1; 122 | self.zoomScale = 1; 123 | 124 | self.contentSize = CGSizeMake(0, 0); 125 | 126 | // Get image from browser as it handles ordering of fetching 127 | UIImage *img = [self.photoBrowser imageForPhoto:_photo]; 128 | if (img) { 129 | // Hide ProgressView 130 | //_progressView.alpha = 0.0f; 131 | [_progressView removeFromSuperview]; 132 | 133 | // Set image 134 | _photoImageView.image = img; 135 | _photoImageView.hidden = NO; 136 | 137 | // Setup photo frame 138 | CGRect photoImageViewFrame; 139 | photoImageViewFrame.origin = CGPointZero; 140 | photoImageViewFrame.size = img.size; 141 | 142 | _photoImageView.frame = photoImageViewFrame; 143 | self.contentSize = photoImageViewFrame.size; 144 | 145 | // Set zoom to minimum zoom 146 | [self setMaxMinZoomScalesForCurrentBounds]; 147 | } else { 148 | // Hide image view 149 | _photoImageView.hidden = YES; 150 | 151 | _progressView.alpha = 1.0f; 152 | } 153 | 154 | [self setNeedsLayout]; 155 | } 156 | } 157 | 158 | - (void)setProgress:(CGFloat)progress forPhoto:(ZYQPhoto*)photo { 159 | ZYQPhoto *p = (ZYQPhoto*)self.photo; 160 | 161 | if ([photo.photoURL.absoluteString isEqualToString:p.photoURL.absoluteString]) { 162 | if ([_progressView conformsToProtocol:objc_getProtocol("ZYQProgressViewProtocol")]) { 163 | if ([(UIView*)_progressView zyq_progress] < progress) { 164 | [(UIView*)_progressView setZyq_progress:progress]; 165 | } 166 | } 167 | else{ 168 | #ifdef NODEPENDENCY 169 | #else 170 | if ([(DACircularProgressView*)_progressView progress] < progress) { 171 | [(DACircularProgressView*)_progressView setProgress:progress animated:YES]; 172 | } 173 | #endif 174 | } 175 | } 176 | } 177 | 178 | // Image failed so just show black! 179 | - (void)displayImageFailure { 180 | [_progressView removeFromSuperview]; 181 | } 182 | 183 | #pragma mark - Setup 184 | 185 | - (void)setMaxMinZoomScalesForCurrentBounds { 186 | // Reset 187 | self.maximumZoomScale = 1; 188 | self.minimumZoomScale = 1; 189 | self.zoomScale = 1; 190 | 191 | // Bail 192 | if (_photoImageView.image == nil) return; 193 | 194 | // Sizes 195 | CGSize boundsSize = self.bounds.size; 196 | boundsSize.width -= 0.1; 197 | boundsSize.height -= 0.1; 198 | 199 | CGSize imageSize = _photoImageView.frame.size; 200 | 201 | // Calculate Min 202 | CGFloat xScale = boundsSize.width / imageSize.width; // the scale needed to perfectly fit the image width-wise 203 | CGFloat yScale = boundsSize.height / imageSize.height; // the scale needed to perfectly fit the image height-wise 204 | CGFloat minScale = MIN(xScale, yScale); // use minimum of these to allow the image to become fully visible 205 | 206 | // If image is smaller than the screen then ensure we show it at 207 | // min scale of 1 208 | if (xScale > 1 && yScale > 1) { 209 | //minScale = 1.0; 210 | } 211 | 212 | // Calculate Max 213 | CGFloat maxScale = 4.0; // Allow double scale 214 | // on high resolution screens we have double the pixel density, so we will be seeing every pixel if we limit the 215 | // maximum zoom scale to 0.5. 216 | if ([UIScreen instancesRespondToSelector:@selector(scale)]) { 217 | maxScale = maxScale / [[UIScreen mainScreen] scale]; 218 | 219 | if (maxScale < minScale) { 220 | maxScale = minScale * 2; 221 | } 222 | } 223 | 224 | // Calculate Max Scale Of Double Tap 225 | CGFloat maxDoubleTapZoomScale = 4.0 * minScale; // Allow double scale 226 | // on high resolution screens we have double the pixel density, so we will be seeing every pixel if we limit the 227 | // maximum zoom scale to 0.5. 228 | if ([UIScreen instancesRespondToSelector:@selector(scale)]) { 229 | maxDoubleTapZoomScale = maxDoubleTapZoomScale / [[UIScreen mainScreen] scale]; 230 | 231 | if (maxDoubleTapZoomScale < minScale) { 232 | maxDoubleTapZoomScale = minScale * 2; 233 | } 234 | } 235 | 236 | // Make sure maxDoubleTapZoomScale isn't larger than maxScale 237 | maxDoubleTapZoomScale = MIN(maxDoubleTapZoomScale, maxScale); 238 | 239 | // Set 240 | self.maximumZoomScale = maxScale; 241 | self.minimumZoomScale = minScale; 242 | self.zoomScale = minScale; 243 | self.maximumDoubleTapZoomScale = maxDoubleTapZoomScale; 244 | 245 | // Reset position 246 | _photoImageView.frame = CGRectMake(0, 0, _photoImageView.frame.size.width, _photoImageView.frame.size.height); 247 | [self setNeedsLayout]; 248 | } 249 | 250 | #pragma mark - Layout 251 | 252 | - (void)layoutSubviews { 253 | // Update tap view frame 254 | _tapView.frame = self.bounds; 255 | 256 | // Super 257 | [super layoutSubviews]; 258 | 259 | // Center the image as it becomes smaller than the size of the screen 260 | CGSize boundsSize = self.bounds.size; 261 | CGRect frameToCenter = _photoImageView.frame; 262 | 263 | // Horizontally 264 | if (frameToCenter.size.width < boundsSize.width) { 265 | frameToCenter.origin.x = floorf((boundsSize.width - frameToCenter.size.width) / 2.0); 266 | } else { 267 | frameToCenter.origin.x = 0; 268 | } 269 | 270 | // Vertically 271 | if (frameToCenter.size.height < boundsSize.height) { 272 | frameToCenter.origin.y = floorf((boundsSize.height - frameToCenter.size.height) / 2.0); 273 | } else { 274 | frameToCenter.origin.y = 0; 275 | } 276 | 277 | // Center 278 | if (!CGRectEqualToRect(_photoImageView.frame, frameToCenter)) 279 | _photoImageView.frame = frameToCenter; 280 | } 281 | 282 | #pragma mark - UIScrollViewDelegate 283 | 284 | - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView { 285 | return _photoImageView; 286 | } 287 | 288 | - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { 289 | [_photoBrowser cancelControlHiding]; 290 | } 291 | 292 | - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view { 293 | [_photoBrowser cancelControlHiding]; 294 | } 295 | 296 | - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { 297 | [_photoBrowser hideControlsAfterDelay]; 298 | } 299 | 300 | - (void)scrollViewDidZoom:(UIScrollView *)scrollView { 301 | [self setNeedsLayout]; 302 | [self layoutIfNeeded]; 303 | } 304 | 305 | #pragma mark - Tap Detection 306 | 307 | - (void)handleSingleTap:(CGPoint)touchPoint { 308 | // [_photoBrowser performSelector:@selector(toggleControls) withObject:nil afterDelay:0.2]; 309 | [_photoBrowser performSelector:@selector(handleSingleTap) withObject:nil afterDelay:0.2]; 310 | } 311 | 312 | - (void)handleDoubleTap:(CGPoint)touchPoint { 313 | 314 | // Cancel any single tap handling 315 | [NSObject cancelPreviousPerformRequestsWithTarget:_photoBrowser]; 316 | 317 | // Zoom 318 | if (round(self.zoomScale*100000) == round(self.maximumDoubleTapZoomScale*100000)) { 319 | 320 | // Zoom out 321 | [self setZoomScale:self.minimumZoomScale animated:YES]; 322 | 323 | } else { 324 | 325 | // Zoom in 326 | CGSize targetSize = CGSizeMake(self.frame.size.width / self.maximumDoubleTapZoomScale, self.frame.size.height / self.maximumDoubleTapZoomScale); 327 | CGPoint targetPoint = CGPointMake(touchPoint.x - targetSize.width / 2.0, touchPoint.y - targetSize.height / 2.0); 328 | 329 | [self zoomToRect:CGRectMake(targetPoint.x, targetPoint.y, targetSize.width, targetSize.height) animated:YES]; 330 | 331 | } 332 | 333 | // Delay controls 334 | [_photoBrowser hideControlsAfterDelay]; 335 | } 336 | 337 | // Image View 338 | - (void)imageView:(UIImageView *)imageView singleTapDetected:(UITouch *)touch { 339 | [self handleSingleTap:[touch locationInView:imageView]]; 340 | } 341 | - (void)imageView:(UIImageView *)imageView doubleTapDetected:(UITouch *)touch { 342 | [self handleDoubleTap:[touch locationInView:imageView]]; 343 | } 344 | 345 | // Background View 346 | - (void)view:(UIView *)view singleTapDetected:(UITouch *)touch { 347 | [self handleSingleTap:[touch locationInView:view]]; 348 | } 349 | - (void)view:(UIView *)view doubleTapDetected:(UITouch *)touch { 350 | [self handleDoubleTap:[touch locationInView:view]]; 351 | } 352 | 353 | @end 354 | --------------------------------------------------------------------------------