├── .gitignore ├── Classes ├── IDMCaptionView.h ├── IDMCaptionView.m ├── IDMPBConstants.h ├── IDMPBLocalizations.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 │ ├── tr.lproj │ │ └── Localizable.strings │ └── zh-Hans.lproj │ │ └── Localizable.strings ├── IDMPhoto.h ├── IDMPhoto.m ├── IDMPhotoBrowser.bundle │ └── images │ │ ├── IDMPhotoBrowser_arrowLeft.png │ │ ├── IDMPhotoBrowser_arrowLeft@2x.png │ │ ├── IDMPhotoBrowser_arrowRight.png │ │ └── IDMPhotoBrowser_arrowRight@2x.png ├── IDMPhotoBrowser.h ├── IDMPhotoBrowser.m ├── IDMPhotoProtocol.h ├── IDMTapDetectingImageView.h ├── IDMTapDetectingImageView.m ├── IDMTapDetectingView.h ├── IDMTapDetectingView.m ├── IDMUtils.h ├── IDMUtils.m ├── IDMZoomingScrollView.h └── IDMZoomingScrollView.m ├── Demo ├── PhotoBrowserDemo.xcodeproj │ └── project.pbxproj ├── PhotoBrowserDemo.xcworkspace │ └── contents.xcworkspacedata ├── PhotoBrowserDemo │ ├── AppDelegate.swift │ ├── Custom Images │ │ ├── IDMPhotoBrowser_customArrowLeft.png │ │ ├── IDMPhotoBrowser_customArrowLeft@2x.png │ │ ├── IDMPhotoBrowser_customArrowLeftSelected.png │ │ ├── IDMPhotoBrowser_customArrowLeftSelected@2x.png │ │ ├── IDMPhotoBrowser_customArrowRight.png │ │ ├── IDMPhotoBrowser_customArrowRight@2x.png │ │ ├── IDMPhotoBrowser_customArrowRightSelected.png │ │ ├── IDMPhotoBrowser_customArrowRightSelected@2x.png │ │ ├── IDMPhotoBrowser_customDoneButton.png │ │ └── IDMPhotoBrowser_customDoneButton@2x.png │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── LaunchImage.launchimage │ │ │ ├── Contents.json │ │ │ ├── Default-568h@2x.png │ │ │ └── Default@2x.png │ ├── Launch Screen.xib │ ├── MenuViewController.swift │ ├── PhotoBrowserDemo-Bridging-Header.h │ ├── PhotoBrowserDemo-Info.plist │ ├── PhotoBrowserDemo-Prefix.pch │ └── Photos │ │ ├── photo1l.jpg │ │ ├── photo1m.jpg │ │ ├── photo2l.jpg │ │ ├── photo2m.jpg │ │ ├── photo3l.jpg │ │ ├── photo3m.jpg │ │ ├── photo4l.jpg │ │ └── photo4m.jpg ├── PhotoBrowserDemoTests │ ├── IDMUtilsTest.m │ ├── Info.plist │ └── PhotoBrowserDemoTests.m ├── Podfile └── Podfile.lock ├── IDMPhotoBrowser.podspec ├── LICENSE.txt ├── README.markdown └── Screenshots ├── idmphotobrowser_ss1.png ├── idmphotobrowser_ss2.png ├── idmphotobrowser_ss3.png ├── idmphotobrowser_ss4.png ├── idmphotobrowser_ss5.png ├── idmphotobrowser_thumb1.png ├── idmphotobrowser_thumb2.png ├── idmphotobrowser_thumb3.png ├── idmphotobrowser_thumb4.png └── idmphotobrowser_thumb5.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | */build/* 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | profile 14 | *.moved-aside 15 | DerivedData 16 | .idea/ 17 | *.hmap 18 | *.xccheckout 19 | 20 | #CocoaPods 21 | Pods -------------------------------------------------------------------------------- /Classes/IDMCaptionView.h: -------------------------------------------------------------------------------- 1 | // 2 | // IDMCaptionView.h 3 | // IDMPhotoBrowser 4 | // 5 | // Created by Michael Waterfall on 30/12/2011. 6 | // Copyright (c) 2011 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "IDMPhotoProtocol.h" 11 | 12 | @interface IDMCaptionView : 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 IDMPhoto 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 | -------------------------------------------------------------------------------- /Classes/IDMCaptionView.m: -------------------------------------------------------------------------------- 1 | // 2 | // IDMCaptionView.m 3 | // IDMPhotoBrowser 4 | // 5 | // Created by Michael Waterfall on 30/12/2011. 6 | // Copyright (c) 2011 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import "IDMCaptionView.h" 10 | #import "IDMPhoto.h" 11 | #import 12 | 13 | static const CGFloat labelPadding = 10; 14 | 15 | // Private 16 | @interface IDMCaptionView () 17 | 18 | @property (nonatomic, strong, readwrite) id photo; 19 | 20 | @end 21 | 22 | @implementation IDMCaptionView 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 | -------------------------------------------------------------------------------- /Classes/IDMPBConstants.h: -------------------------------------------------------------------------------- 1 | // 2 | // IDMPhotoBrowserConstants.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 IDMLog(x, ...) NSLog(x, ## __VA_ARGS__); 16 | #else 17 | #define IDMLog(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 | -------------------------------------------------------------------------------- /Classes/IDMPBLocalizations.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 | -------------------------------------------------------------------------------- /Classes/IDMPBLocalizations.bundle/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Classes/IDMPBLocalizations.bundle/en.lproj/Localizable.strings -------------------------------------------------------------------------------- /Classes/IDMPBLocalizations.bundle/es.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Classes/IDMPBLocalizations.bundle/es.lproj/Localizable.strings -------------------------------------------------------------------------------- /Classes/IDMPBLocalizations.bundle/fr.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Classes/IDMPBLocalizations.bundle/fr.lproj/Localizable.strings -------------------------------------------------------------------------------- /Classes/IDMPBLocalizations.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 | -------------------------------------------------------------------------------- /Classes/IDMPBLocalizations.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" = "Klaar"; 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 | -------------------------------------------------------------------------------- /Classes/IDMPBLocalizations.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 | -------------------------------------------------------------------------------- /Classes/IDMPBLocalizations.bundle/ru.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Classes/IDMPBLocalizations.bundle/ru.lproj/Localizable.strings -------------------------------------------------------------------------------- /Classes/IDMPBLocalizations.bundle/tr.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Classes/IDMPBLocalizations.bundle/tr.lproj/Localizable.strings -------------------------------------------------------------------------------- /Classes/IDMPBLocalizations.bundle/zh-Hans.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Classes/IDMPBLocalizations.bundle/zh-Hans.lproj/Localizable.strings -------------------------------------------------------------------------------- /Classes/IDMPhoto.h: -------------------------------------------------------------------------------- 1 | // 2 | // IDMPhoto.h 3 | // IDMPhotoBrowser 4 | // 5 | // Created by Michael Waterfall on 17/10/2010. 6 | // Copyright 2010 d3i. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "IDMPhotoProtocol.h" 11 | #import 12 | 13 | // This class models a photo/image and it's caption 14 | // If you want to handle photos, caching, decompression 15 | // yourself then you can simply ensure your custom data model 16 | // conforms to IDMPhotoProtocol 17 | @interface IDMPhoto : NSObject 18 | 19 | // Progress download block, used to update the circularView 20 | typedef void (^IDMProgressUpdateBlock)(CGFloat progress); 21 | 22 | // Properties 23 | @property (nonatomic, strong) NSString *caption; 24 | @property (nonatomic, strong) NSURL *photoURL; 25 | @property (nonatomic, strong) IDMProgressUpdateBlock progressUpdateBlock; 26 | @property (nonatomic, strong) UIImage *placeholderImage; 27 | 28 | // Class 29 | + (IDMPhoto *)photoWithImage:(UIImage *)image; 30 | + (IDMPhoto *)photoWithFilePath:(NSString *)path; 31 | + (IDMPhoto *)photoWithURL:(NSURL *)url; 32 | 33 | + (NSArray *)photosWithImages:(NSArray *)imagesArray; 34 | + (NSArray *)photosWithFilePaths:(NSArray *)pathsArray; 35 | + (NSArray *)photosWithURLs:(NSArray *)urlsArray; 36 | 37 | // Init 38 | - (id)initWithImage:(UIImage *)image; 39 | - (id)initWithFilePath:(NSString *)path; 40 | - (id)initWithURL:(NSURL *)url; 41 | 42 | @end 43 | 44 | -------------------------------------------------------------------------------- /Classes/IDMPhoto.m: -------------------------------------------------------------------------------- 1 | // 2 | // IDMPhoto.m 3 | // IDMPhotoBrowser 4 | // 5 | // Created by Michael Waterfall on 17/10/2010. 6 | // Copyright 2010 d3i. All rights reserved. 7 | // 8 | 9 | #import "IDMPhoto.h" 10 | #import "IDMPhotoBrowser.h" 11 | 12 | // Private 13 | @interface IDMPhoto () { 14 | // Image Sources 15 | NSString *_photoPath; 16 | 17 | // Image 18 | UIImage *_underlyingImage; 19 | 20 | // Other 21 | NSString *_caption; 22 | BOOL _loadingInProgress; 23 | } 24 | 25 | // Properties 26 | @property (nonatomic, strong) UIImage *underlyingImage; 27 | 28 | // Methods 29 | - (void)imageLoadingComplete; 30 | 31 | @end 32 | 33 | // IDMPhoto 34 | @implementation IDMPhoto 35 | 36 | // Properties 37 | @synthesize underlyingImage = _underlyingImage, 38 | photoURL = _photoURL, 39 | caption = _caption; 40 | 41 | #pragma mark Class Methods 42 | 43 | + (IDMPhoto *)photoWithImage:(UIImage *)image { 44 | return [[IDMPhoto alloc] initWithImage:image]; 45 | } 46 | 47 | + (IDMPhoto *)photoWithFilePath:(NSString *)path { 48 | return [[IDMPhoto alloc] initWithFilePath:path]; 49 | } 50 | 51 | + (IDMPhoto *)photoWithURL:(NSURL *)url { 52 | return [[IDMPhoto alloc] initWithURL:url]; 53 | } 54 | 55 | + (NSArray *)photosWithImages:(NSArray *)imagesArray { 56 | NSMutableArray *photos = [NSMutableArray arrayWithCapacity:imagesArray.count]; 57 | 58 | for (UIImage *image in imagesArray) { 59 | if ([image isKindOfClass:[UIImage class]]) { 60 | IDMPhoto *photo = [IDMPhoto photoWithImage:image]; 61 | [photos addObject:photo]; 62 | } 63 | } 64 | 65 | return photos; 66 | } 67 | 68 | + (NSArray *)photosWithFilePaths:(NSArray *)pathsArray { 69 | NSMutableArray *photos = [NSMutableArray arrayWithCapacity:pathsArray.count]; 70 | 71 | for (NSString *path in pathsArray) { 72 | if ([path isKindOfClass:[NSString class]]) { 73 | IDMPhoto *photo = [IDMPhoto photoWithFilePath:path]; 74 | [photos addObject:photo]; 75 | } 76 | } 77 | 78 | return photos; 79 | } 80 | 81 | + (NSArray *)photosWithURLs:(NSArray *)urlsArray { 82 | NSMutableArray *photos = [NSMutableArray arrayWithCapacity:urlsArray.count]; 83 | 84 | for (id url in urlsArray) { 85 | if ([url isKindOfClass:[NSURL class]]) { 86 | IDMPhoto *photo = [IDMPhoto photoWithURL:url]; 87 | [photos addObject:photo]; 88 | } 89 | else if ([url isKindOfClass:[NSString class]]) { 90 | IDMPhoto *photo = [IDMPhoto photoWithURL:[NSURL URLWithString:url]]; 91 | [photos addObject:photo]; 92 | } 93 | } 94 | 95 | return photos; 96 | } 97 | 98 | #pragma mark NSObject 99 | 100 | - (id)initWithImage:(UIImage *)image { 101 | if ((self = [super init])) { 102 | self.underlyingImage = image; 103 | } 104 | return self; 105 | } 106 | 107 | - (id)initWithFilePath:(NSString *)path { 108 | if ((self = [super init])) { 109 | _photoPath = [path copy]; 110 | } 111 | return self; 112 | } 113 | 114 | - (id)initWithURL:(NSURL *)url { 115 | if ((self = [super init])) { 116 | _photoURL = [url copy]; 117 | } 118 | return self; 119 | } 120 | 121 | #pragma mark IDMPhoto Protocol Methods 122 | 123 | - (UIImage *)underlyingImage { 124 | return _underlyingImage; 125 | } 126 | 127 | - (void)loadUnderlyingImageAndNotify { 128 | NSAssert([[NSThread currentThread] isMainThread], @"This method must be called on the main thread."); 129 | _loadingInProgress = YES; 130 | if (self.underlyingImage) { 131 | // Image already loaded 132 | [self imageLoadingComplete]; 133 | } else { 134 | if (_photoPath) { 135 | // Load async from file 136 | [self performSelectorInBackground:@selector(loadImageFromFileAsync) withObject:nil]; 137 | } else if (_photoURL) { 138 | // Load async from web (using SDWebImageManager) 139 | 140 | [[SDWebImageManager sharedManager] loadImageWithURL:_photoURL options:SDWebImageRetryFailed progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) { 141 | CGFloat progress = ((CGFloat)receivedSize)/((CGFloat)expectedSize); 142 | 143 | if (self.progressUpdateBlock) { 144 | self.progressUpdateBlock(progress); 145 | } 146 | } completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) { 147 | if (image) { 148 | self.underlyingImage = image; 149 | } 150 | 151 | [self performSelectorOnMainThread:@selector(imageLoadingComplete) withObject:nil waitUntilDone:NO]; 152 | }]; 153 | } else { 154 | // Failed - no source 155 | self.underlyingImage = nil; 156 | [self imageLoadingComplete]; 157 | } 158 | } 159 | } 160 | 161 | // Release if we can get it again from path or url 162 | - (void)unloadUnderlyingImage { 163 | _loadingInProgress = NO; 164 | 165 | if (self.underlyingImage && (_photoPath || _photoURL)) { 166 | self.underlyingImage = nil; 167 | } 168 | } 169 | 170 | #pragma mark - Async Loading 171 | 172 | /*- (UIImage *)decodedImageWithImage:(UIImage *)image { 173 | CGImageRef imageRef = image.CGImage; 174 | // System only supports RGB, set explicitly and prevent context error 175 | // if the downloaded image is not the supported format 176 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 177 | 178 | CGContextRef context = CGBitmapContextCreate(NULL, 179 | CGImageGetWidth(imageRef), 180 | CGImageGetHeight(imageRef), 181 | 8, 182 | // width * 4 will be enough because are in ARGB format, don't read from the image 183 | CGImageGetWidth(imageRef) * 4, 184 | colorSpace, 185 | // kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little 186 | // makes system don't need to do extra conversion when displayed. 187 | kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little); 188 | CGColorSpaceRelease(colorSpace); 189 | 190 | if ( ! context) { 191 | return nil; 192 | } 193 | 194 | CGRect rect = (CGRect){CGPointZero, CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)}; 195 | CGContextDrawImage(context, rect, imageRef); 196 | CGImageRef decompressedImageRef = CGBitmapContextCreateImage(context); 197 | CGContextRelease(context); 198 | 199 | UIImage *decompressedImage = [[UIImage alloc] initWithCGImage:decompressedImageRef]; 200 | CGImageRelease(decompressedImageRef); 201 | return decompressedImage; 202 | }*/ 203 | 204 | - (UIImage *)decodedImageWithImage:(UIImage *)image { 205 | if (image.images) { 206 | // Do not decode animated images 207 | return image; 208 | } 209 | 210 | CGImageRef imageRef = image.CGImage; 211 | CGSize imageSize = CGSizeMake(CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)); 212 | CGRect imageRect = (CGRect){.origin = CGPointZero, .size = imageSize}; 213 | 214 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 215 | CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef); 216 | 217 | int infoMask = (bitmapInfo & kCGBitmapAlphaInfoMask); 218 | BOOL anyNonAlpha = (infoMask == kCGImageAlphaNone || 219 | infoMask == kCGImageAlphaNoneSkipFirst || 220 | infoMask == kCGImageAlphaNoneSkipLast); 221 | 222 | // CGBitmapContextCreate doesn't support kCGImageAlphaNone with RGB. 223 | // https://developer.apple.com/library/mac/#qa/qa1037/_index.html 224 | if (infoMask == kCGImageAlphaNone && CGColorSpaceGetNumberOfComponents(colorSpace) > 1) 225 | { 226 | // Unset the old alpha info. 227 | bitmapInfo &= ~kCGBitmapAlphaInfoMask; 228 | 229 | // Set noneSkipFirst. 230 | bitmapInfo |= kCGImageAlphaNoneSkipFirst; 231 | } 232 | // Some PNGs tell us they have alpha but only 3 components. Odd. 233 | else if (!anyNonAlpha && CGColorSpaceGetNumberOfComponents(colorSpace) == 3) 234 | { 235 | // Unset the old alpha info. 236 | bitmapInfo &= ~kCGBitmapAlphaInfoMask; 237 | bitmapInfo |= kCGImageAlphaPremultipliedFirst; 238 | } 239 | 240 | // It calculates the bytes-per-row based on the bitsPerComponent and width arguments. 241 | CGContextRef context = CGBitmapContextCreate(NULL, 242 | imageSize.width, 243 | imageSize.height, 244 | CGImageGetBitsPerComponent(imageRef), 245 | 0, 246 | colorSpace, 247 | bitmapInfo); 248 | CGColorSpaceRelease(colorSpace); 249 | 250 | // If failed, return undecompressed image 251 | if (!context) return image; 252 | 253 | CGContextDrawImage(context, imageRect, imageRef); 254 | CGImageRef decompressedImageRef = CGBitmapContextCreateImage(context); 255 | 256 | CGContextRelease(context); 257 | 258 | UIImage *decompressedImage = [UIImage imageWithCGImage:decompressedImageRef scale:image.scale orientation:image.imageOrientation]; 259 | CGImageRelease(decompressedImageRef); 260 | return decompressedImage; 261 | } 262 | 263 | // Called in background 264 | // Load image in background from local file 265 | - (void)loadImageFromFileAsync { 266 | @autoreleasepool { 267 | @try { 268 | self.underlyingImage = [UIImage imageWithContentsOfFile:_photoPath]; 269 | if (!_underlyingImage) { 270 | //IDMLog(@"Error loading photo from path: %@", _photoPath); 271 | } 272 | } @finally { 273 | self.underlyingImage = [self decodedImageWithImage: self.underlyingImage]; 274 | [self performSelectorOnMainThread:@selector(imageLoadingComplete) withObject:nil waitUntilDone:NO]; 275 | } 276 | } 277 | } 278 | 279 | // Called on main 280 | - (void)imageLoadingComplete { 281 | NSAssert([[NSThread currentThread] isMainThread], @"This method must be called on the main thread."); 282 | // Complete so notify 283 | _loadingInProgress = NO; 284 | [[NSNotificationCenter defaultCenter] postNotificationName:IDMPhoto_LOADING_DID_END_NOTIFICATION 285 | object:self]; 286 | } 287 | 288 | @end 289 | -------------------------------------------------------------------------------- /Classes/IDMPhotoBrowser.bundle/images/IDMPhotoBrowser_arrowLeft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Classes/IDMPhotoBrowser.bundle/images/IDMPhotoBrowser_arrowLeft.png -------------------------------------------------------------------------------- /Classes/IDMPhotoBrowser.bundle/images/IDMPhotoBrowser_arrowLeft@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Classes/IDMPhotoBrowser.bundle/images/IDMPhotoBrowser_arrowLeft@2x.png -------------------------------------------------------------------------------- /Classes/IDMPhotoBrowser.bundle/images/IDMPhotoBrowser_arrowRight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Classes/IDMPhotoBrowser.bundle/images/IDMPhotoBrowser_arrowRight.png -------------------------------------------------------------------------------- /Classes/IDMPhotoBrowser.bundle/images/IDMPhotoBrowser_arrowRight@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Classes/IDMPhotoBrowser.bundle/images/IDMPhotoBrowser_arrowRight@2x.png -------------------------------------------------------------------------------- /Classes/IDMPhotoBrowser.h: -------------------------------------------------------------------------------- 1 | // 2 | // IDMPhotoBrowser.h 3 | // IDMPhotoBrowser 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 "IDMPhoto.h" 13 | #import "IDMPhotoProtocol.h" 14 | #import "IDMCaptionView.h" 15 | #import "IDMTapDetectingImageView.h" 16 | 17 | // Delgate 18 | @class IDMPhotoBrowser; 19 | @protocol IDMPhotoBrowserDelegate 20 | @optional 21 | - (void)willAppearPhotoBrowser:(IDMPhotoBrowser *)photoBrowser; 22 | - (void)willDisappearPhotoBrowser:(IDMPhotoBrowser *)photoBrowser; 23 | - (void)photoBrowser:(IDMPhotoBrowser *)photoBrowser didShowPhotoAtIndex:(NSUInteger)index; 24 | - (void)photoBrowser:(IDMPhotoBrowser *)photoBrowser didDismissAtPageIndex:(NSUInteger)index; 25 | - (void)photoBrowser:(IDMPhotoBrowser *)photoBrowser willDismissAtPageIndex:(NSUInteger)index; 26 | - (void)photoBrowser:(IDMPhotoBrowser *)photoBrowser didDismissActionSheetWithButtonIndex:(NSUInteger)buttonIndex photoIndex:(NSUInteger)photoIndex; 27 | - (IDMCaptionView *)photoBrowser:(IDMPhotoBrowser *)photoBrowser captionViewForPhotoAtIndex:(NSUInteger)index; 28 | - (void)photoBrowser:(IDMPhotoBrowser *)photoBrowser imageFailed:(NSUInteger)index imageView:(IDMTapDetectingImageView *)imageView; 29 | @end 30 | 31 | // IDMPhotoBrowser 32 | @interface IDMPhotoBrowser : UIViewController 33 | 34 | // Properties 35 | @property (nonatomic, strong) id delegate; 36 | 37 | // Toolbar customization 38 | @property (nonatomic) BOOL displayToolbar; 39 | @property (nonatomic) BOOL displayCounterLabel; 40 | @property (nonatomic) BOOL displayArrowButton; 41 | @property (nonatomic) BOOL displayActionButton; 42 | @property (nonatomic, strong) NSArray *actionButtonTitles; 43 | @property (nonatomic, weak) UIImage *leftArrowImage, *leftArrowSelectedImage; 44 | @property (nonatomic, weak) UIImage *rightArrowImage, *rightArrowSelectedImage; 45 | @property (nonatomic, weak) UIImage *actionButtonImage, *actionButtonSelectedImage; 46 | 47 | // View customization 48 | @property (nonatomic) BOOL displayDoneButton; 49 | @property (nonatomic) BOOL useWhiteBackgroundColor; 50 | @property (nonatomic, weak) UIImage *doneButtonImage; 51 | @property (nonatomic, weak) UIColor *trackTintColor, *progressTintColor; 52 | @property (nonatomic, assign) CGFloat doneButtonRightInset, doneButtonTopInset; 53 | @property (nonatomic, assign) CGSize doneButtonSize; 54 | 55 | @property (nonatomic, weak) UIImage *scaleImage; 56 | 57 | @property (nonatomic) BOOL arrowButtonsChangePhotosAnimated; 58 | 59 | @property (nonatomic) BOOL forceHideStatusBar; 60 | @property (nonatomic) BOOL usePopAnimation; 61 | @property (nonatomic) BOOL disableVerticalSwipe; 62 | 63 | @property (nonatomic) BOOL dismissOnTouch; 64 | 65 | // Default value: true 66 | // Set to false to tell the photo viewer not to hide the interface when scrolling 67 | @property (nonatomic) BOOL autoHideInterface; 68 | 69 | // Defines zooming of the background (default 1.0) 70 | @property (nonatomic) float backgroundScaleFactor; 71 | 72 | // Animation time (default .28) 73 | @property (nonatomic) float animationDuration; 74 | 75 | // Init 76 | - (id)initWithPhotos:(NSArray *)photosArray; 77 | 78 | // Init (animated from view) 79 | - (id)initWithPhotos:(NSArray *)photosArray animatedFromView:(UIView*)view; 80 | 81 | // Init with NSURL objects 82 | - (id)initWithPhotoURLs:(NSArray *)photoURLsArray; 83 | 84 | // Init with NSURL objects (animated from view) 85 | - (id)initWithPhotoURLs:(NSArray *)photoURLsArray animatedFromView:(UIView*)view; 86 | 87 | // Reloads the photo browser and refetches data 88 | - (void)reloadData; 89 | 90 | // Set page that photo browser starts on 91 | - (void)setInitialPageIndex:(NSUInteger)index; 92 | 93 | // Get IDMPhoto at index 94 | - (id)photoAtIndex:(NSUInteger)index; 95 | 96 | @end 97 | -------------------------------------------------------------------------------- /Classes/IDMPhotoBrowser.m: -------------------------------------------------------------------------------- 1 | // 2 | // IDMPhotoBrowser.m 3 | // IDMPhotoBrowser 4 | // 5 | // Created by Michael Waterfall on 14/10/2010. 6 | // Copyright 2010 d3i. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "IDMPhotoBrowser.h" 11 | #import "IDMZoomingScrollView.h" 12 | #import "IDMUtils.h" 13 | 14 | #import "pop/POP.h" 15 | 16 | #ifndef IDMPhotoBrowserLocalizedStrings 17 | #define IDMPhotoBrowserLocalizedStrings(key) \ 18 | NSLocalizedStringFromTableInBundle((key), nil, [NSBundle bundleWithPath:[[NSBundle bundleForClass: [IDMPhotoBrowser class]] pathForResource:@"IDMPBLocalizations" ofType:@"bundle"]], nil) 19 | #endif 20 | 21 | // Private 22 | @interface IDMPhotoBrowser () { 23 | // Data 24 | NSMutableArray *_photos; 25 | 26 | // Views 27 | UIScrollView *_pagingScrollView; 28 | 29 | // Gesture 30 | UIPanGestureRecognizer *_panGesture; 31 | 32 | // Paging 33 | NSMutableSet *_visiblePages, *_recycledPages; 34 | NSUInteger _pageIndexBeforeRotation; 35 | NSUInteger _currentPageIndex; 36 | 37 | // Buttons 38 | UIButton *_doneButton; 39 | 40 | // Toolbar 41 | UIToolbar *_toolbar; 42 | UIBarButtonItem *_previousButton, *_nextButton, *_actionButton; 43 | UIBarButtonItem *_counterButton; 44 | UILabel *_counterLabel; 45 | 46 | // Actions 47 | UIActionSheet *_actionsSheet; 48 | UIActivityViewController *activityViewController; 49 | 50 | // Control 51 | NSTimer *_controlVisibilityTimer; 52 | 53 | // Appearance 54 | //UIStatusBarStyle _previousStatusBarStyle; 55 | BOOL _statusBarOriginallyHidden; 56 | 57 | // Present 58 | UIView *_senderViewForAnimation; 59 | 60 | // Misc 61 | BOOL _performingLayout; 62 | BOOL _rotating; 63 | BOOL _viewIsActive; // active as in it's in the view heirarchy 64 | BOOL _autoHide; 65 | NSInteger _initalPageIndex; 66 | CGFloat _statusBarHeight; 67 | 68 | BOOL _isdraggingPhoto; 69 | 70 | CGRect _senderViewOriginalFrame; 71 | //UIImage *_backgroundScreenshot; 72 | 73 | UIWindow *_applicationWindow; 74 | 75 | // iOS 7 76 | UIViewController *_applicationTopViewController; 77 | int _previousModalPresentationStyle; 78 | } 79 | 80 | // Private Properties 81 | @property (nonatomic, strong) UIActionSheet *actionsSheet; 82 | @property (nonatomic, strong) UIActivityViewController *activityViewController; 83 | 84 | // Private Methods 85 | 86 | // Layout 87 | - (void)performLayout; 88 | 89 | // Paging 90 | - (void)tilePages; 91 | - (BOOL)isDisplayingPageForIndex:(NSUInteger)index; 92 | - (IDMZoomingScrollView *)pageDisplayedAtIndex:(NSUInteger)index; 93 | - (IDMZoomingScrollView *)pageDisplayingPhoto:(id)photo; 94 | - (IDMZoomingScrollView *)dequeueRecycledPage; 95 | - (void)configurePage:(IDMZoomingScrollView *)page forIndex:(NSUInteger)index; 96 | - (void)didStartViewingPageAtIndex:(NSUInteger)index; 97 | 98 | // Frames 99 | - (CGRect)frameForPagingScrollView; 100 | - (CGRect)frameForPageAtIndex:(NSUInteger)index; 101 | - (CGSize)contentSizeForPagingScrollView; 102 | - (CGPoint)contentOffsetForPageAtIndex:(NSUInteger)index; 103 | - (CGRect)frameForToolbarAtOrientation:(UIInterfaceOrientation)orientation; 104 | - (CGRect)frameForDoneButtonAtOrientation:(UIInterfaceOrientation)orientation; 105 | - (CGRect)frameForCaptionView:(IDMCaptionView *)captionView atIndex:(NSUInteger)index; 106 | 107 | // Toolbar 108 | - (void)updateToolbar; 109 | 110 | // Navigation 111 | - (void)jumpToPageAtIndex:(NSUInteger)index; 112 | - (void)gotoPreviousPage; 113 | - (void)gotoNextPage; 114 | 115 | // Controls 116 | - (void)cancelControlHiding; 117 | - (void)hideControlsAfterDelay; 118 | - (void)setControlsHidden:(BOOL)hidden animated:(BOOL)animated permanent:(BOOL)permanent; 119 | //- (void)toggleControls; 120 | - (BOOL)areControlsHidden; 121 | 122 | // Interactions 123 | - (void)handleSingleTap; 124 | 125 | // Data 126 | - (NSUInteger)numberOfPhotos; 127 | - (id)photoAtIndex:(NSUInteger)index; 128 | - (UIImage *)imageForPhoto:(id)photo; 129 | - (void)loadAdjacentPhotosIfNecessary:(id)photo; 130 | - (void)releaseAllUnderlyingPhotos; 131 | 132 | @end 133 | 134 | // IDMPhotoBrowser 135 | @implementation IDMPhotoBrowser 136 | 137 | // Properties 138 | @synthesize displayDoneButton = _displayDoneButton, displayToolbar = _displayToolbar, displayActionButton = _displayActionButton, displayCounterLabel = _displayCounterLabel, useWhiteBackgroundColor = _useWhiteBackgroundColor, doneButtonImage = _doneButtonImage; 139 | @synthesize leftArrowImage = _leftArrowImage, rightArrowImage = _rightArrowImage, leftArrowSelectedImage = _leftArrowSelectedImage, rightArrowSelectedImage = _rightArrowSelectedImage, actionButtonImage = _actionButtonImage, actionButtonSelectedImage = _actionButtonSelectedImage; 140 | @synthesize displayArrowButton = _displayArrowButton, actionButtonTitles = _actionButtonTitles; 141 | @synthesize arrowButtonsChangePhotosAnimated = _arrowButtonsChangePhotosAnimated; 142 | @synthesize forceHideStatusBar = _forceHideStatusBar; 143 | @synthesize usePopAnimation = _usePopAnimation; 144 | @synthesize disableVerticalSwipe = _disableVerticalSwipe; 145 | @synthesize dismissOnTouch = _dismissOnTouch; 146 | @synthesize actionsSheet = _actionsSheet, activityViewController = _activityViewController; 147 | @synthesize trackTintColor = _trackTintColor, progressTintColor = _progressTintColor; 148 | @synthesize delegate = _delegate; 149 | 150 | #pragma mark - NSObject 151 | 152 | - (id)init { 153 | if ((self = [super init])) { 154 | // Defaults 155 | self.hidesBottomBarWhenPushed = YES; 156 | 157 | _currentPageIndex = 0; 158 | _performingLayout = NO; // Reset on view did appear 159 | _rotating = NO; 160 | _viewIsActive = NO; 161 | _visiblePages = [NSMutableSet new]; 162 | _recycledPages = [NSMutableSet new]; 163 | _photos = [NSMutableArray new]; 164 | 165 | _initalPageIndex = 0; 166 | _autoHide = YES; 167 | _autoHideInterface = YES; 168 | 169 | _displayDoneButton = YES; 170 | _doneButtonImage = nil; 171 | 172 | _displayToolbar = YES; 173 | _displayActionButton = YES; 174 | _displayArrowButton = YES; 175 | _displayCounterLabel = NO; 176 | 177 | _forceHideStatusBar = NO; 178 | _usePopAnimation = NO; 179 | _disableVerticalSwipe = NO; 180 | 181 | _dismissOnTouch = NO; 182 | 183 | _useWhiteBackgroundColor = NO; 184 | _leftArrowImage = _rightArrowImage = _leftArrowSelectedImage = _rightArrowSelectedImage = nil; 185 | 186 | _arrowButtonsChangePhotosAnimated = YES; 187 | 188 | _backgroundScaleFactor = 1.0; 189 | _animationDuration = 0.28; 190 | _senderViewForAnimation = nil; 191 | _scaleImage = nil; 192 | 193 | _isdraggingPhoto = NO; 194 | 195 | _statusBarHeight = 20.f; 196 | _doneButtonRightInset = 20.f; 197 | // relative to status bar and safeAreaInsets 198 | _doneButtonTopInset = 10.f; 199 | 200 | _doneButtonSize = CGSizeMake(55.f, 26.f); 201 | 202 | if ([self respondsToSelector:@selector(automaticallyAdjustsScrollViewInsets)]) { 203 | self.automaticallyAdjustsScrollViewInsets = NO; 204 | } 205 | 206 | _applicationWindow = [[[UIApplication sharedApplication] delegate] window]; 207 | self.modalPresentationStyle = UIModalPresentationCustom; 208 | self.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; 209 | self.modalPresentationCapturesStatusBarAppearance = YES; 210 | self.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; 211 | 212 | // Listen for IDMPhoto notifications 213 | [[NSNotificationCenter defaultCenter] addObserver:self 214 | selector:@selector(handleIDMPhotoLoadingDidEndNotification:) 215 | name:IDMPhoto_LOADING_DID_END_NOTIFICATION 216 | object:nil]; 217 | } 218 | 219 | return self; 220 | } 221 | 222 | - (id)initWithPhotos:(NSArray *)photosArray { 223 | if ((self = [self init])) { 224 | _photos = [[NSMutableArray alloc] initWithArray:photosArray]; 225 | } 226 | return self; 227 | } 228 | 229 | - (id)initWithPhotos:(NSArray *)photosArray animatedFromView:(UIView*)view { 230 | if ((self = [self init])) { 231 | _photos = [[NSMutableArray alloc] initWithArray:photosArray]; 232 | _senderViewForAnimation = view; 233 | } 234 | return self; 235 | } 236 | 237 | - (id)initWithPhotoURLs:(NSArray *)photoURLsArray { 238 | if ((self = [self init])) { 239 | NSArray *photosArray = [IDMPhoto photosWithURLs:photoURLsArray]; 240 | _photos = [[NSMutableArray alloc] initWithArray:photosArray]; 241 | } 242 | return self; 243 | } 244 | 245 | - (id)initWithPhotoURLs:(NSArray *)photoURLsArray animatedFromView:(UIView*)view { 246 | if ((self = [self init])) { 247 | NSArray *photosArray = [IDMPhoto photosWithURLs:photoURLsArray]; 248 | _photos = [[NSMutableArray alloc] initWithArray:photosArray]; 249 | _senderViewForAnimation = view; 250 | } 251 | return self; 252 | } 253 | 254 | - (void)dealloc { 255 | _pagingScrollView.delegate = nil; 256 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 257 | [self releaseAllUnderlyingPhotos]; 258 | } 259 | 260 | - (void)releaseAllUnderlyingPhotos { 261 | for (id p in _photos) { if (p != [NSNull null]) [p unloadUnderlyingImage]; } // Release photos 262 | } 263 | 264 | - (void)didReceiveMemoryWarning { 265 | // Release any cached data, images, etc that aren't in use. 266 | [self releaseAllUnderlyingPhotos]; 267 | [_recycledPages removeAllObjects]; 268 | 269 | // Releases the view if it doesn't have a superview. 270 | [super didReceiveMemoryWarning]; 271 | } 272 | 273 | #pragma mark - Pan Gesture 274 | 275 | - (void)panGestureRecognized:(id)sender { 276 | // Initial Setup 277 | IDMZoomingScrollView *scrollView = [self pageDisplayedAtIndex:_currentPageIndex]; 278 | //IDMTapDetectingImageView *scrollView.photoImageView = scrollView.photoImageView; 279 | 280 | static float firstX, firstY; 281 | 282 | float viewHeight = scrollView.frame.size.height; 283 | float viewHalfHeight = viewHeight/2; 284 | 285 | CGPoint translatedPoint = [(UIPanGestureRecognizer*)sender translationInView:self.view]; 286 | 287 | // Gesture Began 288 | if ([(UIPanGestureRecognizer*)sender state] == UIGestureRecognizerStateBegan) { 289 | [self setControlsHidden:YES animated:YES permanent:YES]; 290 | 291 | firstX = [scrollView center].x; 292 | firstY = [scrollView center].y; 293 | 294 | _senderViewForAnimation.hidden = (_currentPageIndex == _initalPageIndex); 295 | 296 | _isdraggingPhoto = YES; 297 | [self setNeedsStatusBarAppearanceUpdate]; 298 | } 299 | 300 | translatedPoint = CGPointMake(firstX, firstY+translatedPoint.y); 301 | [scrollView setCenter:translatedPoint]; 302 | 303 | float newY = scrollView.center.y - viewHalfHeight; 304 | float newAlpha = 1 - fabsf(newY)/viewHeight; //abs(newY)/viewHeight * 1.8; 305 | 306 | self.view.opaque = YES; 307 | 308 | self.view.backgroundColor = [UIColor colorWithWhite:(_useWhiteBackgroundColor ? 1 : 0) alpha:newAlpha]; 309 | 310 | // Gesture Ended 311 | if ([(UIPanGestureRecognizer*)sender state] == UIGestureRecognizerStateEnded) { 312 | if(scrollView.center.y > viewHalfHeight+40 || scrollView.center.y < viewHalfHeight-40) // Automatic Dismiss View 313 | { 314 | if (_senderViewForAnimation && _currentPageIndex == _initalPageIndex) { 315 | [self performCloseAnimationWithScrollView:scrollView]; 316 | return; 317 | } 318 | 319 | CGFloat finalX = firstX, finalY; 320 | 321 | CGFloat windowsHeigt = [_applicationWindow frame].size.height; 322 | 323 | if(scrollView.center.y > viewHalfHeight+30) // swipe down 324 | finalY = windowsHeigt*2; 325 | else // swipe up 326 | finalY = -viewHalfHeight; 327 | 328 | CGFloat animationDuration = 0.35; 329 | 330 | [UIView beginAnimations:nil context:NULL]; 331 | [UIView setAnimationDuration:animationDuration]; 332 | [UIView setAnimationCurve:UIViewAnimationCurveEaseIn]; 333 | [UIView setAnimationDelegate:self]; 334 | [scrollView setCenter:CGPointMake(finalX, finalY)]; 335 | self.view.backgroundColor = [UIColor colorWithWhite:0 alpha:0]; 336 | [UIView commitAnimations]; 337 | 338 | [self performSelector:@selector(doneButtonPressed:) withObject:self afterDelay:animationDuration]; 339 | } 340 | else // Continue Showing View 341 | { 342 | _isdraggingPhoto = NO; 343 | [self setNeedsStatusBarAppearanceUpdate]; 344 | 345 | self.view.backgroundColor = [UIColor colorWithWhite:(_useWhiteBackgroundColor ? 1 : 0) alpha:1]; 346 | 347 | CGFloat velocityY = (.35*[(UIPanGestureRecognizer*)sender velocityInView:self.view].y); 348 | 349 | CGFloat finalX = firstX; 350 | CGFloat finalY = viewHalfHeight; 351 | 352 | CGFloat animationDuration = (ABS(velocityY)*.0002)+.2; 353 | 354 | [UIView beginAnimations:nil context:NULL]; 355 | [UIView setAnimationDuration:animationDuration]; 356 | [UIView setAnimationCurve:UIViewAnimationCurveEaseOut]; 357 | [UIView setAnimationDelegate:self]; 358 | [scrollView setCenter:CGPointMake(finalX, finalY)]; 359 | [UIView commitAnimations]; 360 | } 361 | } 362 | } 363 | 364 | #pragma mark - Animation 365 | 366 | - (void)performPresentAnimation { 367 | self.view.alpha = 0.0f; 368 | _pagingScrollView.alpha = 0.0f; 369 | 370 | UIImage *imageFromView = _scaleImage ? _scaleImage : [self getImageFromView:_senderViewForAnimation]; 371 | 372 | _senderViewOriginalFrame = [_senderViewForAnimation.superview convertRect:_senderViewForAnimation.frame toView:nil]; 373 | 374 | UIView *fadeView = [[UIView alloc] initWithFrame:_applicationWindow.bounds]; 375 | fadeView.backgroundColor = [UIColor clearColor]; 376 | [_applicationWindow addSubview:fadeView]; 377 | 378 | UIImageView *resizableImageView = [[UIImageView alloc] initWithImage:imageFromView]; 379 | resizableImageView.frame = _senderViewOriginalFrame; 380 | resizableImageView.clipsToBounds = YES; 381 | resizableImageView.contentMode = _senderViewForAnimation ? _senderViewForAnimation.contentMode : UIViewContentModeScaleAspectFill; 382 | resizableImageView.backgroundColor = [UIColor clearColor]; 383 | if (@available(iOS 11.0, *)) { 384 | resizableImageView.accessibilityIgnoresInvertColors = YES; 385 | } else { 386 | // Fallback on earlier versions 387 | } 388 | [_applicationWindow addSubview:resizableImageView]; 389 | _senderViewForAnimation.hidden = YES; 390 | 391 | void (^completion)() = ^() { 392 | self.view.alpha = 1.0f; 393 | _pagingScrollView.alpha = 1.0f; 394 | resizableImageView.backgroundColor = [UIColor colorWithWhite:(_useWhiteBackgroundColor) ? 1 : 0 alpha:1]; 395 | [fadeView removeFromSuperview]; 396 | [resizableImageView removeFromSuperview]; 397 | }; 398 | 399 | [UIView animateWithDuration:_animationDuration animations:^{ 400 | fadeView.backgroundColor = self.useWhiteBackgroundColor ? [UIColor whiteColor] : [UIColor blackColor]; 401 | } completion:nil]; 402 | 403 | CGRect finalImageViewFrame = [self animationFrameForImage:imageFromView presenting:YES scrollView:nil]; 404 | 405 | if(_usePopAnimation) 406 | { 407 | [self animateView:resizableImageView 408 | toFrame:finalImageViewFrame 409 | completion:completion]; 410 | } 411 | else 412 | { 413 | [UIView animateWithDuration:_animationDuration animations:^{ 414 | resizableImageView.layer.frame = finalImageViewFrame; 415 | } completion:^(BOOL finished) { 416 | completion(); 417 | }]; 418 | } 419 | } 420 | 421 | - (void)performCloseAnimationWithScrollView:(IDMZoomingScrollView*)scrollView { 422 | if ([_delegate respondsToSelector:@selector(willDisappearPhotoBrowser:)]) { 423 | [_delegate willDisappearPhotoBrowser:self]; 424 | } 425 | 426 | float fadeAlpha = 1 - fabs(scrollView.frame.origin.y)/scrollView.frame.size.height; 427 | 428 | UIImage *imageFromView = [scrollView.photo underlyingImage]; 429 | if (!imageFromView && [scrollView.photo respondsToSelector:@selector(placeholderImage)]) { 430 | imageFromView = [scrollView.photo placeholderImage]; 431 | } 432 | 433 | UIView *fadeView = [[UIView alloc] initWithFrame:_applicationWindow.bounds]; 434 | fadeView.backgroundColor = self.useWhiteBackgroundColor ? [UIColor whiteColor] : [UIColor blackColor]; 435 | fadeView.alpha = fadeAlpha; 436 | [_applicationWindow addSubview:fadeView]; 437 | 438 | CGRect imageViewFrame = [self animationFrameForImage:imageFromView presenting:NO scrollView:scrollView]; 439 | 440 | UIImageView *resizableImageView = [[UIImageView alloc] initWithImage:imageFromView]; 441 | resizableImageView.frame = imageViewFrame; 442 | resizableImageView.contentMode = _senderViewForAnimation ? _senderViewForAnimation.contentMode : UIViewContentModeScaleAspectFill; 443 | resizableImageView.backgroundColor = [UIColor clearColor]; 444 | resizableImageView.clipsToBounds = YES; 445 | if (@available(iOS 11.0, *)) { 446 | resizableImageView.accessibilityIgnoresInvertColors = YES; 447 | } else { 448 | // Fallback on earlier versions 449 | } 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(_usePopAnimation) 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 | CGRect bounds = _applicationWindow.bounds; 497 | // adjust bounds as the photo browser does 498 | if (@available(iOS 11.0, *)) { 499 | // use the windows safe area inset 500 | UIWindow *window = [UIApplication sharedApplication].keyWindow; 501 | UIEdgeInsets insets = UIEdgeInsetsMake(_statusBarHeight, 0, 0, 0); 502 | if (window != NULL) { 503 | insets = window.safeAreaInsets; 504 | } 505 | bounds = [self adjustForSafeArea:bounds adjustForStatusBar:NO forInsets:insets]; 506 | } 507 | CGFloat maxWidth = CGRectGetWidth(bounds); 508 | CGFloat maxHeight = CGRectGetHeight(bounds); 509 | 510 | CGRect animationFrame = CGRectZero; 511 | 512 | CGFloat aspect = imageSize.width / imageSize.height; 513 | if (maxWidth / aspect <= maxHeight) { 514 | animationFrame.size = CGSizeMake(maxWidth, maxWidth / aspect); 515 | } 516 | else { 517 | animationFrame.size = CGSizeMake(maxHeight * aspect, maxHeight); 518 | } 519 | 520 | animationFrame.origin.x = roundf((maxWidth - animationFrame.size.width) / 2.0f); 521 | animationFrame.origin.y = roundf((maxHeight - animationFrame.size.height) / 2.0f); 522 | 523 | if (!presenting) { 524 | animationFrame.origin.y += scrollView.frame.origin.y; 525 | } 526 | return animationFrame; 527 | } 528 | 529 | #pragma mark - Genaral 530 | 531 | - (void)prepareForClosePhotoBrowser { 532 | // Gesture 533 | [_applicationWindow removeGestureRecognizer:_panGesture]; 534 | 535 | _autoHide = NO; 536 | 537 | // Controls 538 | [NSObject cancelPreviousPerformRequestsWithTarget:self]; // Cancel any pending toggles from taps 539 | } 540 | 541 | - (void)dismissPhotoBrowserAnimated:(BOOL)animated { 542 | self.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; 543 | 544 | if ([_delegate respondsToSelector:@selector(photoBrowser:willDismissAtPageIndex:)]) 545 | [_delegate photoBrowser:self willDismissAtPageIndex:_currentPageIndex]; 546 | 547 | [self dismissViewControllerAnimated:animated completion:^{ 548 | if ([_delegate respondsToSelector:@selector(photoBrowser:didDismissAtPageIndex:)]) 549 | [_delegate photoBrowser:self didDismissAtPageIndex:_currentPageIndex]; 550 | 551 | // if (SYSTEM_VERSION_LESS_THAN(@"8.0")) 552 | // { 553 | // _applicationTopViewController.modalPresentationStyle = _previousModalPresentationStyle; 554 | // } 555 | }]; 556 | } 557 | 558 | - (UIButton*)customToolbarButtonImage:(UIImage*)image imageSelected:(UIImage*)selectedImage action:(SEL)action { 559 | UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; 560 | [button setImage:image forState:UIControlStateNormal]; 561 | [button setImage:selectedImage forState:UIControlStateDisabled]; 562 | [button addTarget:self action:action forControlEvents:UIControlEventTouchUpInside]; 563 | [button setContentMode:UIViewContentModeCenter]; 564 | [button setFrame:[self getToolbarButtonFrame:image]]; 565 | return button; 566 | } 567 | 568 | - (CGRect)getToolbarButtonFrame:(UIImage *)image{ 569 | BOOL const isRetinaHd = ((float)[[UIScreen mainScreen] scale] > 2.0f); 570 | float const defaultButtonSize = isRetinaHd ? 66.0f : 44.0f; 571 | CGFloat buttonWidth = (image.size.width > defaultButtonSize) ? image.size.width : defaultButtonSize; 572 | CGFloat buttonHeight = (image.size.height > defaultButtonSize) ? image.size.width : defaultButtonSize; 573 | return CGRectMake(0,0, buttonWidth, buttonHeight); 574 | } 575 | 576 | - (UIImage*)getImageFromView:(UIView *)view { 577 | UIGraphicsBeginImageContextWithOptions(view.bounds.size, YES, 2); 578 | [view.layer renderInContext:UIGraphicsGetCurrentContext()]; 579 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 580 | UIGraphicsEndImageContext(); 581 | return image; 582 | } 583 | 584 | - (UIViewController *)topviewController 585 | { 586 | UIViewController *topviewController = [UIApplication sharedApplication].keyWindow.rootViewController; 587 | 588 | while (topviewController.presentedViewController) { 589 | topviewController = topviewController.presentedViewController; 590 | } 591 | 592 | return topviewController; 593 | } 594 | 595 | #pragma mark - View Lifecycle 596 | 597 | - (void)viewDidLoad { 598 | // View 599 | self.view.backgroundColor = [UIColor colorWithWhite:(_useWhiteBackgroundColor ? 1 : 0) alpha:1]; 600 | 601 | self.view.clipsToBounds = YES; 602 | 603 | // Setup paging scrolling view 604 | CGRect pagingScrollViewFrame = [self frameForPagingScrollView]; 605 | _pagingScrollView = [[UIScrollView alloc] initWithFrame:pagingScrollViewFrame]; 606 | //_pagingScrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 607 | _pagingScrollView.pagingEnabled = YES; 608 | _pagingScrollView.delegate = self; 609 | _pagingScrollView.showsHorizontalScrollIndicator = NO; 610 | _pagingScrollView.showsVerticalScrollIndicator = NO; 611 | _pagingScrollView.backgroundColor = [UIColor clearColor]; 612 | _pagingScrollView.contentSize = [self contentSizeForPagingScrollView]; 613 | [self.view addSubview:_pagingScrollView]; 614 | 615 | // Transition animation 616 | [self performPresentAnimation]; 617 | 618 | UIInterfaceOrientation currentOrientation = [UIApplication sharedApplication].statusBarOrientation; 619 | 620 | // Toolbar 621 | _toolbar = [[UIToolbar alloc] initWithFrame:[self frameForToolbarAtOrientation:currentOrientation]]; 622 | _toolbar.backgroundColor = [UIColor clearColor]; 623 | _toolbar.clipsToBounds = YES; 624 | _toolbar.translucent = YES; 625 | [_toolbar setBackgroundImage:[UIImage new] 626 | forToolbarPosition:UIToolbarPositionAny 627 | barMetrics:UIBarMetricsDefault]; 628 | 629 | // Close Button 630 | _doneButton = [UIButton buttonWithType:UIButtonTypeCustom]; 631 | [_doneButton setFrame:[self frameForDoneButtonAtOrientation:currentOrientation]]; 632 | [_doneButton setAlpha:1.0f]; 633 | [_doneButton addTarget:self action:@selector(doneButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; 634 | 635 | if(!_doneButtonImage) { 636 | [_doneButton setTitleColor:[UIColor colorWithWhite:0.9 alpha:0.9] forState:UIControlStateNormal|UIControlStateHighlighted]; 637 | [_doneButton setTitle:IDMPhotoBrowserLocalizedStrings(@"Done") forState:UIControlStateNormal]; 638 | [_doneButton.titleLabel setFont:[UIFont boldSystemFontOfSize:11.0f]]; 639 | [_doneButton setBackgroundColor:[UIColor colorWithWhite:0.1 alpha:0.5]]; 640 | _doneButton.layer.cornerRadius = 3.0f; 641 | _doneButton.layer.borderColor = [UIColor colorWithWhite:0.9 alpha:0.9].CGColor; 642 | _doneButton.layer.borderWidth = 1.0f; 643 | _doneButtonSize = _doneButton.frame.size; 644 | } 645 | else { 646 | [_doneButton setImage:_doneButtonImage forState:UIControlStateNormal]; 647 | _doneButton.contentMode = UIViewContentModeScaleAspectFit; 648 | } 649 | 650 | UIImage *leftButtonImage = (_leftArrowImage == nil) ? 651 | [UIImage imageNamed:@"IDMPhotoBrowser.bundle/images/IDMPhotoBrowser_arrowLeft.png"] : _leftArrowImage; 652 | 653 | UIImage *rightButtonImage = (_rightArrowImage == nil) ? 654 | [UIImage imageNamed:@"IDMPhotoBrowser.bundle/images/IDMPhotoBrowser_arrowRight.png"] : _rightArrowImage; 655 | 656 | UIImage *leftButtonSelectedImage = (_leftArrowSelectedImage == nil) ? 657 | [UIImage imageNamed:@"IDMPhotoBrowser.bundle/images/IDMPhotoBrowser_arrowLeftSelected.png"] : _leftArrowSelectedImage; 658 | 659 | UIImage *rightButtonSelectedImage = (_rightArrowSelectedImage == nil) ? 660 | [UIImage imageNamed:@"IDMPhotoBrowser.bundle/images/IDMPhotoBrowser_arrowRightSelected.png"] : _rightArrowSelectedImage; 661 | 662 | // Arrows 663 | _previousButton = [[UIBarButtonItem alloc] initWithCustomView:[self customToolbarButtonImage:leftButtonImage 664 | imageSelected:leftButtonSelectedImage 665 | action:@selector(gotoPreviousPage)]]; 666 | 667 | _nextButton = [[UIBarButtonItem alloc] initWithCustomView:[self customToolbarButtonImage:rightButtonImage 668 | imageSelected:rightButtonSelectedImage 669 | action:@selector(gotoNextPage)]]; 670 | 671 | // Counter Label 672 | _counterLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 95, 40)]; 673 | _counterLabel.textAlignment = NSTextAlignmentCenter; 674 | _counterLabel.backgroundColor = [UIColor clearColor]; 675 | _counterLabel.font = [UIFont fontWithName:@"Helvetica" size:17]; 676 | 677 | if(_useWhiteBackgroundColor == NO) { 678 | _counterLabel.textColor = [UIColor whiteColor]; 679 | _counterLabel.shadowColor = [UIColor darkTextColor]; 680 | _counterLabel.shadowOffset = CGSizeMake(0, 1); 681 | } 682 | else { 683 | _counterLabel.textColor = [UIColor blackColor]; 684 | } 685 | 686 | // Counter Button 687 | _counterButton = [[UIBarButtonItem alloc] initWithCustomView:_counterLabel]; 688 | 689 | // Action Button 690 | if(_actionButtonImage != nil && _actionButtonSelectedImage != nil) { 691 | _actionButton = [[UIBarButtonItem alloc] initWithCustomView:[self customToolbarButtonImage:_actionButtonImage 692 | imageSelected:_actionButtonSelectedImage 693 | action:@selector(actionButtonPressed:)]]; 694 | } 695 | else { 696 | _actionButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction 697 | target:self 698 | action:@selector(actionButtonPressed:)]; 699 | } 700 | 701 | // Gesture 702 | _panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureRecognized:)]; 703 | [_panGesture setMinimumNumberOfTouches:1]; 704 | [_panGesture setMaximumNumberOfTouches:1]; 705 | 706 | // Update 707 | //[self reloadData]; 708 | 709 | // Super 710 | [super viewDidLoad]; 711 | } 712 | 713 | - (void)viewWillAppear:(BOOL)animated { 714 | // Update 715 | [self reloadData]; 716 | 717 | 718 | if ([_delegate respondsToSelector:@selector(willAppearPhotoBrowser:)]) { 719 | [_delegate willAppearPhotoBrowser:self]; 720 | } 721 | 722 | // Super 723 | [super viewWillAppear:animated]; 724 | 725 | // Status Bar 726 | _statusBarOriginallyHidden = [UIApplication sharedApplication].statusBarHidden; 727 | 728 | // Update UI 729 | [self hideControlsAfterDelay]; 730 | } 731 | 732 | - (void)viewDidAppear:(BOOL)animated { 733 | [super viewDidAppear:animated]; 734 | _viewIsActive = YES; 735 | } 736 | 737 | // Release any retained subviews of the main view. 738 | - (void)viewDidUnload { 739 | _currentPageIndex = 0; 740 | _pagingScrollView = nil; 741 | _visiblePages = nil; 742 | _recycledPages = nil; 743 | _toolbar = nil; 744 | _doneButton = nil; 745 | _previousButton = nil; 746 | _nextButton = nil; 747 | 748 | [super viewDidUnload]; 749 | } 750 | 751 | #pragma mark - Status Bar 752 | 753 | - (UIStatusBarStyle)preferredStatusBarStyle { 754 | return _useWhiteBackgroundColor ? UIStatusBarStyleDefault : UIStatusBarStyleLightContent; 755 | } 756 | 757 | - (BOOL)prefersStatusBarHidden { 758 | if(_forceHideStatusBar) { 759 | return YES; 760 | } 761 | 762 | if(_isdraggingPhoto) { 763 | if(_statusBarOriginallyHidden) { 764 | return YES; 765 | } 766 | else { 767 | return NO; 768 | } 769 | } 770 | else { 771 | return [self areControlsHidden]; 772 | } 773 | } 774 | 775 | - (UIStatusBarAnimation)preferredStatusBarUpdateAnimation { 776 | return UIStatusBarAnimationFade; 777 | } 778 | 779 | #pragma mark - Layout 780 | 781 | - (void)viewWillLayoutSubviews { 782 | // Flag 783 | _performingLayout = YES; 784 | 785 | UIInterfaceOrientation currentOrientation = [UIApplication sharedApplication].statusBarOrientation; 786 | 787 | // Toolbar 788 | _toolbar.frame = [self frameForToolbarAtOrientation:currentOrientation]; 789 | 790 | // Done button 791 | _doneButton.frame = [self frameForDoneButtonAtOrientation:currentOrientation]; 792 | 793 | 794 | // Remember index 795 | NSUInteger indexPriorToLayout = _currentPageIndex; 796 | 797 | // Get paging scroll view frame to determine if anything needs changing 798 | CGRect pagingScrollViewFrame = [self frameForPagingScrollView]; 799 | 800 | // Frame needs changing 801 | _pagingScrollView.frame = pagingScrollViewFrame; 802 | 803 | // Recalculate contentSize based on current orientation 804 | _pagingScrollView.contentSize = [self contentSizeForPagingScrollView]; 805 | 806 | // Adjust frames and configuration of each visible page 807 | for (IDMZoomingScrollView *page in _visiblePages) { 808 | NSUInteger index = PAGE_INDEX(page); 809 | page.frame = [self frameForPageAtIndex:index]; 810 | page.captionView.frame = [self frameForCaptionView:page.captionView atIndex:index]; 811 | [page setMaxMinZoomScalesForCurrentBounds]; 812 | } 813 | 814 | // Adjust contentOffset to preserve page location based on values collected prior to location 815 | _pagingScrollView.contentOffset = [self contentOffsetForPageAtIndex:indexPriorToLayout]; 816 | [self didStartViewingPageAtIndex:_currentPageIndex]; // initial 817 | 818 | // Reset 819 | _currentPageIndex = indexPriorToLayout; 820 | _performingLayout = NO; 821 | 822 | // Super 823 | [super viewWillLayoutSubviews]; 824 | } 825 | 826 | - (void)performLayout { 827 | // Setup 828 | _performingLayout = YES; 829 | NSUInteger numberOfPhotos = [self numberOfPhotos]; 830 | 831 | // Setup pages 832 | [_visiblePages removeAllObjects]; 833 | [_recycledPages removeAllObjects]; 834 | 835 | // Toolbar 836 | if (_displayToolbar) { 837 | [self.view addSubview:_toolbar]; 838 | } else { 839 | [_toolbar removeFromSuperview]; 840 | } 841 | 842 | // Close button 843 | if(_displayDoneButton && !self.navigationController.navigationBar) 844 | [self.view addSubview:_doneButton]; 845 | 846 | // Toolbar items & navigation 847 | UIBarButtonItem *fixedLeftSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace 848 | target:self action:nil]; 849 | fixedLeftSpace.width = 32; // To balance action button 850 | UIBarButtonItem *flexSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace 851 | target:self action:nil]; 852 | NSMutableArray *items = [NSMutableArray new]; 853 | 854 | if (_displayActionButton) 855 | [items addObject:fixedLeftSpace]; 856 | [items addObject:flexSpace]; 857 | 858 | if (numberOfPhotos > 1 && _displayArrowButton) 859 | [items addObject:_previousButton]; 860 | 861 | if(_displayCounterLabel) { 862 | [items addObject:flexSpace]; 863 | [items addObject:_counterButton]; 864 | } 865 | 866 | [items addObject:flexSpace]; 867 | if (numberOfPhotos > 1 && _displayArrowButton) 868 | [items addObject:_nextButton]; 869 | [items addObject:flexSpace]; 870 | 871 | if(_displayActionButton) 872 | [items addObject:_actionButton]; 873 | 874 | [_toolbar setItems:items]; 875 | [self updateToolbar]; 876 | 877 | // Content offset 878 | _pagingScrollView.contentOffset = [self contentOffsetForPageAtIndex:_currentPageIndex]; 879 | [self tilePages]; 880 | _performingLayout = NO; 881 | 882 | if(! _disableVerticalSwipe) { 883 | [self.view addGestureRecognizer:_panGesture]; 884 | } 885 | } 886 | 887 | #pragma mark - Data 888 | 889 | - (void)reloadData { 890 | // Get data 891 | [self releaseAllUnderlyingPhotos]; 892 | 893 | // Update 894 | [self performLayout]; 895 | 896 | // Layout 897 | [self.view setNeedsLayout]; 898 | } 899 | 900 | - (NSUInteger)numberOfPhotos { 901 | return _photos.count; 902 | } 903 | 904 | - (id)photoAtIndex:(NSUInteger)index { 905 | return _photos[index]; 906 | } 907 | 908 | - (IDMCaptionView *)captionViewForPhotoAtIndex:(NSUInteger)index { 909 | IDMCaptionView *captionView = nil; 910 | if ([_delegate respondsToSelector:@selector(photoBrowser:captionViewForPhotoAtIndex:)]) { 911 | captionView = [_delegate photoBrowser:self captionViewForPhotoAtIndex:index]; 912 | } else { 913 | id photo = [self photoAtIndex:index]; 914 | if ([photo respondsToSelector:@selector(caption)]) { 915 | if ([photo caption]) captionView = [[IDMCaptionView alloc] initWithPhoto:photo]; 916 | } 917 | } 918 | captionView.alpha = [self areControlsHidden] ? 0 : 1; // Initial alpha 919 | 920 | return captionView; 921 | } 922 | 923 | - (UIImage *)imageForPhoto:(id)photo { 924 | if (photo) { 925 | // Get image or obtain in background 926 | if ([photo underlyingImage]) { 927 | return [photo underlyingImage]; 928 | } else { 929 | [photo loadUnderlyingImageAndNotify]; 930 | if ([photo respondsToSelector:@selector(placeholderImage)]) { 931 | return [photo placeholderImage]; 932 | } 933 | } 934 | } 935 | 936 | return nil; 937 | } 938 | 939 | - (void)loadAdjacentPhotosIfNecessary:(id)photo { 940 | IDMZoomingScrollView *page = [self pageDisplayingPhoto:photo]; 941 | if (page) { 942 | // If page is current page then initiate loading of previous and next pages 943 | NSUInteger pageIndex = PAGE_INDEX(page); 944 | if (_currentPageIndex == pageIndex) { 945 | if (pageIndex > 0) { 946 | // Preload index - 1 947 | id photo = [self photoAtIndex:pageIndex-1]; 948 | if (![photo underlyingImage]) { 949 | [photo loadUnderlyingImageAndNotify]; 950 | IDMLog(@"Pre-loading image at index %i", pageIndex-1); 951 | } 952 | } 953 | if (pageIndex < [self numberOfPhotos] - 1) { 954 | // Preload index + 1 955 | id photo = [self photoAtIndex:pageIndex+1]; 956 | if (![photo underlyingImage]) { 957 | [photo loadUnderlyingImageAndNotify]; 958 | IDMLog(@"Pre-loading image at index %i", pageIndex+1); 959 | } 960 | } 961 | } 962 | } 963 | } 964 | 965 | #pragma mark - IDMPhoto Loading Notification 966 | 967 | - (void)handleIDMPhotoLoadingDidEndNotification:(NSNotification *)notification { 968 | id photo = [notification object]; 969 | IDMZoomingScrollView *page = [self pageDisplayingPhoto:photo]; 970 | if (page) { 971 | if ([photo underlyingImage]) { 972 | // Successful load 973 | [page displayImage]; 974 | [self loadAdjacentPhotosIfNecessary:photo]; 975 | } else { 976 | // Failed to load 977 | [page displayImageFailure]; 978 | if ([_delegate respondsToSelector:@selector(photoBrowser:imageFailed:imageView:)]) { 979 | NSUInteger pageIndex = PAGE_INDEX(page); 980 | [_delegate photoBrowser:self imageFailed:pageIndex imageView:page.photoImageView]; 981 | } 982 | // make sure the page is completely updated 983 | [page setNeedsLayout]; 984 | } 985 | } 986 | } 987 | 988 | #pragma mark - Paging 989 | 990 | - (void)tilePages { 991 | // Calculate which pages should be visible 992 | // Ignore padding as paging bounces encroach on that 993 | // and lead to false page loads 994 | CGRect visibleBounds = _pagingScrollView.bounds; 995 | NSInteger iFirstIndex = (NSInteger) floorf((CGRectGetMinX(visibleBounds)+PADDING*2) / CGRectGetWidth(visibleBounds)); 996 | NSInteger iLastIndex = (NSInteger) floorf((CGRectGetMaxX(visibleBounds)-PADDING*2-1) / CGRectGetWidth(visibleBounds)); 997 | if (iFirstIndex < 0) iFirstIndex = 0; 998 | if (iFirstIndex > [self numberOfPhotos] - 1) iFirstIndex = [self numberOfPhotos] - 1; 999 | if (iLastIndex < 0) iLastIndex = 0; 1000 | if (iLastIndex > [self numberOfPhotos] - 1) iLastIndex = [self numberOfPhotos] - 1; 1001 | 1002 | // Recycle no longer needed pages 1003 | NSInteger pageIndex; 1004 | for (IDMZoomingScrollView *page in _visiblePages) { 1005 | pageIndex = PAGE_INDEX(page); 1006 | if (pageIndex < (NSUInteger)iFirstIndex || pageIndex > (NSUInteger)iLastIndex) { 1007 | [_recycledPages addObject:page]; 1008 | [page prepareForReuse]; 1009 | [page removeFromSuperview]; 1010 | IDMLog(@"Removed page at index %i", PAGE_INDEX(page)); 1011 | } 1012 | } 1013 | [_visiblePages minusSet:_recycledPages]; 1014 | while (_recycledPages.count > 2) // Only keep 2 recycled pages 1015 | [_recycledPages removeObject:[_recycledPages anyObject]]; 1016 | 1017 | // Add missing pages 1018 | for (NSUInteger index = (NSUInteger)iFirstIndex; index <= (NSUInteger)iLastIndex; index++) { 1019 | if (![self isDisplayingPageForIndex:index]) { 1020 | // Add new page 1021 | IDMZoomingScrollView *page; 1022 | page = [[IDMZoomingScrollView alloc] initWithPhotoBrowser:self]; 1023 | page.backgroundColor = [UIColor clearColor]; 1024 | page.opaque = YES; 1025 | 1026 | [self configurePage:page forIndex:index]; 1027 | [_visiblePages addObject:page]; 1028 | [_pagingScrollView addSubview:page]; 1029 | IDMLog(@"Added page at index %i", index); 1030 | 1031 | // Add caption 1032 | IDMCaptionView *captionView = [self captionViewForPhotoAtIndex:index]; 1033 | captionView.frame = [self frameForCaptionView:captionView atIndex:index]; 1034 | [_pagingScrollView addSubview:captionView]; 1035 | page.captionView = captionView; 1036 | } 1037 | } 1038 | } 1039 | 1040 | - (BOOL)isDisplayingPageForIndex:(NSUInteger)index { 1041 | for (IDMZoomingScrollView *page in _visiblePages) 1042 | if (PAGE_INDEX(page) == index) return YES; 1043 | return NO; 1044 | } 1045 | 1046 | - (IDMZoomingScrollView *)pageDisplayedAtIndex:(NSUInteger)index { 1047 | IDMZoomingScrollView *thePage = nil; 1048 | for (IDMZoomingScrollView *page in _visiblePages) { 1049 | if (PAGE_INDEX(page) == index) { 1050 | thePage = page; break; 1051 | } 1052 | } 1053 | return thePage; 1054 | } 1055 | 1056 | - (IDMZoomingScrollView *)pageDisplayingPhoto:(id)photo { 1057 | IDMZoomingScrollView *thePage = nil; 1058 | for (IDMZoomingScrollView *page in _visiblePages) { 1059 | if (page.photo == photo) { 1060 | thePage = page; break; 1061 | } 1062 | } 1063 | return thePage; 1064 | } 1065 | 1066 | - (void)configurePage:(IDMZoomingScrollView *)page forIndex:(NSUInteger)index { 1067 | page.frame = [self frameForPageAtIndex:index]; 1068 | page.tag = PAGE_INDEX_TAG_OFFSET + index; 1069 | page.photo = [self photoAtIndex:index]; 1070 | 1071 | __block __weak IDMPhoto *photo = (IDMPhoto*)page.photo; 1072 | __weak IDMZoomingScrollView* weakPage = page; 1073 | photo.progressUpdateBlock = ^(CGFloat progress){ 1074 | [weakPage setProgress:progress forPhoto:photo]; 1075 | }; 1076 | } 1077 | 1078 | - (IDMZoomingScrollView *)dequeueRecycledPage { 1079 | IDMZoomingScrollView *page = [_recycledPages anyObject]; 1080 | if (page) { 1081 | [_recycledPages removeObject:page]; 1082 | } 1083 | return page; 1084 | } 1085 | 1086 | // Handle page changes 1087 | - (void)didStartViewingPageAtIndex:(NSUInteger)index { 1088 | // Load adjacent images if needed and the photo is already 1089 | // loaded. Also called after photo has been loaded in background 1090 | id currentPhoto = [self photoAtIndex:index]; 1091 | if ([currentPhoto underlyingImage]) { 1092 | // photo loaded so load ajacent now 1093 | [self loadAdjacentPhotosIfNecessary:currentPhoto]; 1094 | } 1095 | if ([_delegate respondsToSelector:@selector(photoBrowser:didShowPhotoAtIndex:)]) { 1096 | [_delegate photoBrowser:self didShowPhotoAtIndex:index]; 1097 | } 1098 | } 1099 | 1100 | #pragma mark - Frame Calculations 1101 | 1102 | - (CGRect)frameForPagingScrollView { 1103 | CGRect frame = self.view.bounds; 1104 | frame.origin.x -= PADDING; 1105 | frame.size.width += (2 * PADDING); 1106 | frame = [self adjustForSafeArea:frame adjustForStatusBar:false]; 1107 | return frame; 1108 | } 1109 | 1110 | - (CGRect)frameForPageAtIndex:(NSUInteger)index { 1111 | // We have to use our paging scroll view's bounds, not frame, to calculate the page placement. When the device is in 1112 | // landscape orientation, the frame will still be in portrait because the pagingScrollView is the root view controller's 1113 | // view, so its frame is in window coordinate space, which is never rotated. Its bounds, however, will be in landscape 1114 | // because it has a rotation transform applied. 1115 | CGRect bounds = _pagingScrollView.bounds; 1116 | CGRect pageFrame = bounds; 1117 | pageFrame.size.width -= (2 * PADDING); 1118 | pageFrame.origin.x = (bounds.size.width * index) + PADDING; 1119 | return pageFrame; 1120 | } 1121 | 1122 | - (CGSize)contentSizeForPagingScrollView { 1123 | // We have to use the paging scroll view's bounds to calculate the contentSize, for the same reason outlined above. 1124 | CGRect bounds = _pagingScrollView.bounds; 1125 | return CGSizeMake(bounds.size.width * [self numberOfPhotos], bounds.size.height); 1126 | } 1127 | 1128 | - (CGPoint)contentOffsetForPageAtIndex:(NSUInteger)index { 1129 | CGFloat pageWidth = _pagingScrollView.bounds.size.width; 1130 | CGFloat newOffset = index * pageWidth; 1131 | return CGPointMake(newOffset, 0); 1132 | } 1133 | 1134 | - (BOOL)isLandscape:(UIInterfaceOrientation)orientation 1135 | { 1136 | return UIInterfaceOrientationIsLandscape(orientation); 1137 | } 1138 | 1139 | - (CGRect)frameForToolbarAtOrientation:(UIInterfaceOrientation)orientation { 1140 | CGFloat height = 44; 1141 | 1142 | if ([self isLandscape:orientation]) 1143 | height = 32; 1144 | 1145 | CGRect rtn = CGRectMake(0, self.view.bounds.size.height - height, self.view.bounds.size.width, height); 1146 | rtn = [self adjustForSafeArea:rtn adjustForStatusBar:true]; 1147 | return rtn; 1148 | } 1149 | 1150 | - (CGRect)frameForDoneButtonAtOrientation:(UIInterfaceOrientation)orientation { 1151 | CGRect screenBound = self.view.bounds; 1152 | CGFloat screenWidth = screenBound.size.width; 1153 | 1154 | CGRect rtn = CGRectMake(screenWidth - self.doneButtonRightInset - self.doneButtonSize.width, self.doneButtonTopInset, self.doneButtonSize.width, self.doneButtonSize.height); 1155 | rtn = [self adjustForSafeArea:rtn adjustForStatusBar:true]; 1156 | return rtn; 1157 | } 1158 | 1159 | - (CGRect)frameForCaptionView:(IDMCaptionView *)captionView atIndex:(NSUInteger)index { 1160 | CGRect pageFrame = [self frameForPageAtIndex:index]; 1161 | 1162 | CGSize captionSize = [captionView sizeThatFits:CGSizeMake(pageFrame.size.width, 0)]; 1163 | CGRect captionFrame = CGRectMake(pageFrame.origin.x, pageFrame.size.height - captionSize.height - (_toolbar.superview?_toolbar.frame.size.height:0), pageFrame.size.width, captionSize.height); 1164 | 1165 | return captionFrame; 1166 | } 1167 | 1168 | - (CGRect)adjustForSafeArea:(CGRect)rect adjustForStatusBar:(BOOL)adjust { 1169 | if (@available(iOS 11.0, *)) { 1170 | return [self adjustForSafeArea:rect adjustForStatusBar:adjust forInsets:self.view.safeAreaInsets]; 1171 | } 1172 | UIEdgeInsets insets = UIEdgeInsetsMake(_statusBarHeight, 0, 0, 0); 1173 | return [self adjustForSafeArea:rect adjustForStatusBar:adjust forInsets:insets]; 1174 | } 1175 | 1176 | - (CGRect)adjustForSafeArea:(CGRect)rect adjustForStatusBar:(BOOL)adjust forInsets:(UIEdgeInsets) insets { 1177 | return [IDMUtils adjustRect:rect forSafeAreaInsets:insets forBounds:self.view.bounds adjustForStatusBar:adjust statusBarHeight:_statusBarHeight]; 1178 | } 1179 | 1180 | #pragma mark - UIScrollView Delegate 1181 | 1182 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView { 1183 | // Checks 1184 | if (!_viewIsActive || _performingLayout || _rotating) return; 1185 | 1186 | // Tile pages 1187 | [self tilePages]; 1188 | 1189 | // Calculate current page 1190 | CGRect visibleBounds = _pagingScrollView.bounds; 1191 | NSInteger index = (NSInteger) (floorf(CGRectGetMidX(visibleBounds) / CGRectGetWidth(visibleBounds))); 1192 | if (index < 0) index = 0; 1193 | if (index > [self numberOfPhotos] - 1) index = [self numberOfPhotos] - 1; 1194 | NSUInteger previousCurrentPage = _currentPageIndex; 1195 | _currentPageIndex = index; 1196 | if (_currentPageIndex != previousCurrentPage) { 1197 | [self didStartViewingPageAtIndex:index]; 1198 | 1199 | if(_arrowButtonsChangePhotosAnimated) [self updateToolbar]; 1200 | } 1201 | } 1202 | 1203 | - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { 1204 | // Hide controls when dragging begins 1205 | if(_autoHideInterface){ 1206 | [self setControlsHidden:YES animated:YES permanent:NO]; 1207 | } 1208 | } 1209 | 1210 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { 1211 | // Update toolbar when page changes 1212 | if(! _arrowButtonsChangePhotosAnimated) [self updateToolbar]; 1213 | } 1214 | 1215 | #pragma mark - Toolbar 1216 | 1217 | - (void)updateToolbar { 1218 | // Counter 1219 | if ([self numberOfPhotos] > 1) { 1220 | _counterLabel.text = [NSString stringWithFormat:@"%lu %@ %lu", (unsigned long)(_currentPageIndex+1), IDMPhotoBrowserLocalizedStrings(@"of"), (unsigned long)[self numberOfPhotos]]; 1221 | } else { 1222 | _counterLabel.text = nil; 1223 | } 1224 | 1225 | // Buttons 1226 | _previousButton.enabled = (_currentPageIndex > 0); 1227 | _nextButton.enabled = (_currentPageIndex < [self numberOfPhotos]-1); 1228 | } 1229 | 1230 | - (void)jumpToPageAtIndex:(NSUInteger)index { 1231 | // Change page 1232 | if (index < [self numberOfPhotos]) { 1233 | CGRect pageFrame = [self frameForPageAtIndex:index]; 1234 | 1235 | if(_arrowButtonsChangePhotosAnimated) 1236 | { 1237 | [_pagingScrollView setContentOffset:CGPointMake(pageFrame.origin.x - PADDING, 0) animated:YES]; 1238 | } 1239 | else 1240 | { 1241 | _pagingScrollView.contentOffset = CGPointMake(pageFrame.origin.x - PADDING, 0); 1242 | [self updateToolbar]; 1243 | } 1244 | } 1245 | 1246 | // Update timer to give more time 1247 | [self hideControlsAfterDelay]; 1248 | } 1249 | 1250 | - (void)gotoPreviousPage { [self jumpToPageAtIndex:_currentPageIndex-1]; } 1251 | - (void)gotoNextPage { [self jumpToPageAtIndex:_currentPageIndex+1]; } 1252 | 1253 | #pragma mark - Control Hiding / Showing 1254 | 1255 | // If permanent then we don't set timers to hide again 1256 | - (void)setControlsHidden:(BOOL)hidden animated:(BOOL)animated permanent:(BOOL)permanent { 1257 | // Cancel any timers 1258 | [self cancelControlHiding]; 1259 | 1260 | // Captions 1261 | NSMutableSet *captionViews = [[NSMutableSet alloc] initWithCapacity:_visiblePages.count]; 1262 | for (IDMZoomingScrollView *page in _visiblePages) { 1263 | if (page.captionView) [captionViews addObject:page.captionView]; 1264 | } 1265 | 1266 | // Hide/show bars 1267 | [UIView animateWithDuration:(animated ? 0.1 : 0) animations:^(void) { 1268 | CGFloat alpha = hidden ? 0 : 1; 1269 | [self.navigationController.navigationBar setAlpha:alpha]; 1270 | [_toolbar setAlpha:alpha]; 1271 | [_doneButton setAlpha:alpha]; 1272 | for (UIView *v in captionViews) v.alpha = alpha; 1273 | } completion:^(BOOL finished) {}]; 1274 | 1275 | // Control hiding timer 1276 | // Will cancel existing timer but only begin hiding if they are visible 1277 | if (!permanent) { 1278 | [self hideControlsAfterDelay]; 1279 | } 1280 | 1281 | [self setNeedsStatusBarAppearanceUpdate]; 1282 | } 1283 | 1284 | - (void)cancelControlHiding { 1285 | // If a timer exists then cancel and release 1286 | if (_controlVisibilityTimer) { 1287 | [_controlVisibilityTimer invalidate]; 1288 | _controlVisibilityTimer = nil; 1289 | } 1290 | } 1291 | 1292 | // Enable/disable control visiblity timer 1293 | - (void)hideControlsAfterDelay { 1294 | if (![self autoHideInterface]) { 1295 | return; 1296 | } 1297 | 1298 | if (![self areControlsHidden]) { 1299 | [self cancelControlHiding]; 1300 | _controlVisibilityTimer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(hideControls) userInfo:nil repeats:NO]; 1301 | } 1302 | } 1303 | 1304 | - (BOOL)areControlsHidden { 1305 | return (_toolbar.alpha == 0); 1306 | } 1307 | 1308 | - (void)hideControls { 1309 | if(_autoHide && _autoHideInterface) { 1310 | [self setControlsHidden:YES animated:YES permanent:NO]; 1311 | } 1312 | } 1313 | - (void)handleSingleTap { 1314 | if (_dismissOnTouch) { 1315 | [self doneButtonPressed:nil]; 1316 | } else { 1317 | [self setControlsHidden:![self areControlsHidden] animated:YES permanent:NO]; 1318 | } 1319 | } 1320 | 1321 | 1322 | #pragma mark - Properties 1323 | 1324 | - (void)setInitialPageIndex:(NSUInteger)index { 1325 | // Validate 1326 | if (index >= [self numberOfPhotos]) index = [self numberOfPhotos]-1; 1327 | _initalPageIndex = index; 1328 | _currentPageIndex = index; 1329 | if ([self isViewLoaded]) { 1330 | [self jumpToPageAtIndex:index]; 1331 | if (!_viewIsActive) [self tilePages]; // Force tiling if view is not visible 1332 | } 1333 | } 1334 | 1335 | #pragma mark - Buttons 1336 | 1337 | - (void)doneButtonPressed:(id)sender { 1338 | if ([_delegate respondsToSelector:@selector(willDisappearPhotoBrowser:)]) { 1339 | [_delegate willDisappearPhotoBrowser:self]; 1340 | } 1341 | 1342 | if (_senderViewForAnimation && _currentPageIndex == _initalPageIndex) { 1343 | IDMZoomingScrollView *scrollView = [self pageDisplayedAtIndex:_currentPageIndex]; 1344 | [self performCloseAnimationWithScrollView:scrollView]; 1345 | } 1346 | else { 1347 | _senderViewForAnimation.hidden = NO; 1348 | [self prepareForClosePhotoBrowser]; 1349 | [self dismissPhotoBrowserAnimated:YES]; 1350 | } 1351 | } 1352 | 1353 | - (void)actionButtonPressed:(id)sender { 1354 | id photo = [self photoAtIndex:_currentPageIndex]; 1355 | 1356 | if ([self numberOfPhotos] > 0 && [photo underlyingImage]) { 1357 | if(!_actionButtonTitles) 1358 | { 1359 | // Activity view 1360 | NSMutableArray *activityItems = [NSMutableArray arrayWithObject:[photo underlyingImage]]; 1361 | if (photo.caption) [activityItems addObject:photo.caption]; 1362 | 1363 | self.activityViewController = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:nil]; 1364 | 1365 | __typeof__(self) __weak selfBlock = self; 1366 | 1367 | [self.activityViewController setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { 1368 | [selfBlock hideControlsAfterDelay]; 1369 | selfBlock.activityViewController = nil; 1370 | }]; 1371 | 1372 | if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { 1373 | [self presentViewController:self.activityViewController animated:YES completion:nil]; 1374 | } 1375 | else { // iPad 1376 | UIPopoverController *popover = [[UIPopoverController alloc] initWithContentViewController:self.activityViewController]; 1377 | [popover presentPopoverFromRect:CGRectMake(self.view.frame.size.width/2, self.view.frame.size.height/4, 0, 0) 1378 | inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny 1379 | animated:YES]; 1380 | } 1381 | } 1382 | else 1383 | { 1384 | // Action sheet 1385 | self.actionsSheet = [UIActionSheet new]; 1386 | self.actionsSheet.delegate = self; 1387 | for(NSString *action in _actionButtonTitles) { 1388 | [self.actionsSheet addButtonWithTitle:action]; 1389 | } 1390 | 1391 | self.actionsSheet.cancelButtonIndex = [self.actionsSheet addButtonWithTitle:IDMPhotoBrowserLocalizedStrings(@"Cancel")]; 1392 | self.actionsSheet.actionSheetStyle = UIActionSheetStyleBlackTranslucent; 1393 | 1394 | if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { 1395 | [_actionsSheet showInView:self.view]; 1396 | } else { 1397 | [_actionsSheet showFromBarButtonItem:sender animated:YES]; 1398 | } 1399 | } 1400 | 1401 | // Keep controls hidden 1402 | [self setControlsHidden:NO animated:YES permanent:YES]; 1403 | } 1404 | } 1405 | 1406 | #pragma mark - Action Sheet Delegate 1407 | 1408 | - (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex { 1409 | if (actionSheet == _actionsSheet) { 1410 | self.actionsSheet = nil; 1411 | 1412 | if (buttonIndex != actionSheet.cancelButtonIndex) { 1413 | if ([_delegate respondsToSelector:@selector(photoBrowser:didDismissActionSheetWithButtonIndex:photoIndex:)]) { 1414 | [_delegate photoBrowser:self didDismissActionSheetWithButtonIndex:buttonIndex photoIndex:_currentPageIndex]; 1415 | return; 1416 | } 1417 | } 1418 | } 1419 | 1420 | [self hideControlsAfterDelay]; // Continue as normal... 1421 | } 1422 | 1423 | #pragma mark - pop Animation 1424 | 1425 | - (void)animateView:(UIView *)view toFrame:(CGRect)frame completion:(void (^)(void))completion 1426 | { 1427 | POPSpringAnimation *animation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewFrame]; 1428 | [animation setSpringBounciness:6]; 1429 | [animation setDynamicsMass:1]; 1430 | [animation setToValue:[NSValue valueWithCGRect:frame]]; 1431 | [view pop_addAnimation:animation forKey:nil]; 1432 | 1433 | if (completion) 1434 | { 1435 | [animation setCompletionBlock:^(POPAnimation *animation, BOOL finished) { 1436 | completion(); 1437 | }]; 1438 | } 1439 | } 1440 | @end 1441 | -------------------------------------------------------------------------------- /Classes/IDMPhotoProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // IDMPhotoProtocol.h 3 | // IDMPhotoBrowser 4 | // 5 | // Created by Michael Waterfall on 02/01/2012. 6 | // Copyright (c) 2012 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "IDMPBConstants.h" 11 | 12 | // Name of notification used when a photo has completed loading process 13 | // Used to notify browser display the image 14 | #define IDMPhoto_LOADING_DID_END_NOTIFICATION @"IDMPhoto_LOADING_DID_END_NOTIFICATION" 15 | 16 | // If you wish to use your own data models for photo then they must conform 17 | // to this protocol. See instructions for details on each method. 18 | // Otherwise you can use the IDMPhoto object or subclass it yourself to 19 | // store more information per photo. 20 | // 21 | // You can see the IDMPhoto class for an example implementation of this protocol 22 | // 23 | @protocol IDMPhoto 24 | 25 | @required 26 | 27 | // Return underlying UIImage to be displayed 28 | // Return nil if the image is not immediately available (loaded into memory, preferably 29 | // already decompressed) and needs to be loaded from a source (cache, file, web, etc) 30 | // IMPORTANT: You should *NOT* use this method to initiate 31 | // fetching of images from any external of source. That should be handled 32 | // in -loadUnderlyingImageAndNotify: which may be called by the photo browser if this 33 | // methods returns nil. 34 | - (UIImage *)underlyingImage; 35 | 36 | // Called when the browser has determined the underlying images is not 37 | // already loaded into memory but needs it. 38 | // You must load the image asyncronously (and decompress it for better performance). 39 | // See IDMPhoto object for an example implementation. 40 | // When the underlying UIImage is loaded (or failed to load) you should post the following 41 | // notification: 42 | // 43 | // [[NSNotificationCenter defaultCenter] postNotificationName:IDMPhoto_LOADING_DID_END_NOTIFICATION 44 | // object:self]; 45 | // 46 | - (void)loadUnderlyingImageAndNotify; 47 | 48 | // This is called when the photo browser has determined the photo data 49 | // is no longer needed or there are low memory conditions 50 | // You should release any underlying (possibly large and decompressed) image data 51 | // as long as the image can be re-loaded (from cache, file, or URL) 52 | - (void)unloadUnderlyingImage; 53 | 54 | @optional 55 | 56 | // Return a caption string to be displayed over the image 57 | // Return nil to display no caption 58 | - (NSString *)caption; 59 | 60 | // Return placeholder UIImage to be displayed while loading underlyingImage 61 | // Return nil if there is no placeholder 62 | - (UIImage *)placeholderImage; 63 | 64 | @end -------------------------------------------------------------------------------- /Classes/IDMTapDetectingImageView.h: -------------------------------------------------------------------------------- 1 | // 2 | // IDMTapDetectingImageView.h 3 | // IDMPhotoBrowser 4 | // 5 | // Created by Michael Waterfall on 04/11/2009. 6 | // Copyright 2009 d3i. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol IDMTapDetectingImageViewDelegate; 12 | 13 | @interface IDMTapDetectingImageView : UIImageView { 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 IDMTapDetectingImageViewDelegate 23 | @optional 24 | - (void)imageView:(UIImageView *)imageView singleTapDetected:(UITouch *)touch; 25 | - (void)imageView:(UIImageView *)imageView doubleTapDetected:(UITouch *)touch; 26 | - (void)imageView:(UIImageView *)imageView tripleTapDetected:(UITouch *)touch; 27 | @end -------------------------------------------------------------------------------- /Classes/IDMTapDetectingImageView.m: -------------------------------------------------------------------------------- 1 | // 2 | // IDMTapDetectingImageView.m 3 | // IDMPhotoBrowser 4 | // 5 | // Created by Michael Waterfall on 04/11/2009. 6 | // Copyright 2009 d3i. All rights reserved. 7 | // 8 | 9 | #import "IDMTapDetectingImageView.h" 10 | 11 | @implementation IDMTapDetectingImageView 12 | 13 | @synthesize tapDelegate; 14 | 15 | - (id)initWithFrame:(CGRect)frame { 16 | if ((self = [super initWithFrame:frame])) { 17 | self.userInteractionEnabled = YES; 18 | } 19 | return self; 20 | } 21 | 22 | - (id)initWithImage:(UIImage *)image { 23 | if ((self = [super initWithImage:image])) { 24 | self.userInteractionEnabled = YES; 25 | } 26 | return self; 27 | } 28 | 29 | - (id)initWithImage:(UIImage *)image highlightedImage:(UIImage *)highlightedImage { 30 | if ((self = [super initWithImage:image highlightedImage:highlightedImage])) { 31 | self.userInteractionEnabled = YES; 32 | } 33 | return self; 34 | } 35 | 36 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { 37 | UITouch *touch = [touches anyObject]; 38 | NSUInteger tapCount = touch.tapCount; 39 | switch (tapCount) { 40 | case 1: 41 | [self handleSingleTap:touch]; 42 | break; 43 | case 2: 44 | [self handleDoubleTap:touch]; 45 | break; 46 | case 3: 47 | [self handleTripleTap:touch]; 48 | break; 49 | default: 50 | break; 51 | } 52 | [[self nextResponder] touchesEnded:touches withEvent:event]; 53 | } 54 | 55 | - (void)handleSingleTap:(UITouch *)touch { 56 | if ([tapDelegate respondsToSelector:@selector(imageView:singleTapDetected:)]) 57 | [tapDelegate imageView:self singleTapDetected:touch]; 58 | } 59 | 60 | - (void)handleDoubleTap:(UITouch *)touch { 61 | if ([tapDelegate respondsToSelector:@selector(imageView:doubleTapDetected:)]) 62 | [tapDelegate imageView:self doubleTapDetected:touch]; 63 | } 64 | 65 | - (void)handleTripleTap:(UITouch *)touch { 66 | if ([tapDelegate respondsToSelector:@selector(imageView:tripleTapDetected:)]) 67 | [tapDelegate imageView:self tripleTapDetected:touch]; 68 | } 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /Classes/IDMTapDetectingView.h: -------------------------------------------------------------------------------- 1 | // 2 | // IDMTapDetectingView.h 3 | // IDMPhotoBrowser 4 | // 5 | // Created by Michael Waterfall on 04/11/2009. 6 | // Copyright 2009 d3i. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol IDMTapDetectingViewDelegate; 12 | 13 | @interface IDMTapDetectingView : 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 IDMTapDetectingViewDelegate 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 -------------------------------------------------------------------------------- /Classes/IDMTapDetectingView.m: -------------------------------------------------------------------------------- 1 | // 2 | // IDMTapDetectingView.m 3 | // IDMPhotoBrowser 4 | // 5 | // Created by Michael Waterfall on 04/11/2009. 6 | // Copyright 2009 d3i. All rights reserved. 7 | // 8 | 9 | #import "IDMTapDetectingView.h" 10 | 11 | @implementation IDMTapDetectingView 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 | -------------------------------------------------------------------------------- /Classes/IDMUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // IDMUtils.h 3 | // PhotoBrowserDemo 4 | // 5 | // Created by Oliver ONeill on 2/12/17. 6 | // 7 | 8 | #import 9 | 10 | @interface IDMUtils : NSObject 11 | + (CGRect)adjustRect:(CGRect)rect forSafeAreaInsets:(UIEdgeInsets)insets forBounds:(CGRect)bounds adjustForStatusBar:(BOOL)adjust statusBarHeight:(int)statusBarHeight; 12 | @end 13 | -------------------------------------------------------------------------------- /Classes/IDMUtils.m: -------------------------------------------------------------------------------- 1 | // 2 | // IDMUtils.m 3 | // PhotoBrowserDemo 4 | // 5 | // Created by Oliver ONeill on 2/12/17. 6 | // 7 | 8 | #import "IDMUtils.h" 9 | 10 | @implementation IDMUtils 11 | /** 12 | * Adjust a rect to be moved into a safe area specified by `insets`. 13 | * 14 | * NOTE: this does not cover all cases. Given a rect it will reposition it if it 15 | * falls into an unsafe area according to `insets` and `bounds`. When 16 | * `adjustForStatusBar` is true, the rect y position will be based from the edge 17 | * of the safe area, otherwise it will be based from zero. This allows views to 18 | * sit behind the status bar. Status bar height is also used 19 | * to keep positioning consistent when toggling the status bar on and off 20 | */ 21 | + (CGRect)adjustRect:(CGRect)rect forSafeAreaInsets:(UIEdgeInsets)insets forBounds:(CGRect)bounds adjustForStatusBar:(BOOL)adjust statusBarHeight:(int)statusBarHeight { 22 | BOOL isLeft = rect.origin.x <= insets.left; 23 | // If the safe area is not specified via insets we should fall back to the 24 | // status bar height 25 | CGFloat insetTop = insets.top > 0 ? insets.top : statusBarHeight; 26 | // Don't adjust for y positioning when adjustForStatusBar is false 27 | BOOL isAtTop = (rect.origin.y <= insetTop); 28 | BOOL isRight = rect.origin.x + rect.size.width >= bounds.size.width - insets.right; 29 | BOOL isAtBottom = rect.origin.y + rect.size.height >= bounds.size.height - insets.bottom; 30 | if ((isLeft) && (isRight)) { 31 | rect.origin.x += insets.left; 32 | rect.size.width -= insets.right + insets.left; 33 | } else if (isLeft) { 34 | rect.origin.x += insets.left; 35 | } else if (isRight) { 36 | rect.origin.x -= insets.right; 37 | } 38 | // if we're adjusting for status bar then we should move the view out of 39 | // the inset 40 | if ((adjust) && (isAtTop) && (isAtBottom)) { 41 | rect.origin.y += insetTop; 42 | rect.size.height -= insets.bottom + insetTop; 43 | } else if ((adjust) && (isAtTop)) { 44 | rect.origin.y += insetTop; 45 | } else if ((isAtTop) && (isAtBottom)) { 46 | rect.size.height -= insets.bottom; 47 | } else if (isAtBottom) { 48 | rect.origin.y -= insets.bottom; 49 | } 50 | return rect; 51 | } 52 | @end 53 | -------------------------------------------------------------------------------- /Classes/IDMZoomingScrollView.h: -------------------------------------------------------------------------------- 1 | // 2 | // IDMZoomingScrollView.h 3 | // IDMPhotoBrowser 4 | // 5 | // Created by Michael Waterfall on 14/10/2010. 6 | // Copyright 2010 d3i. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "IDMPhotoProtocol.h" 11 | #import "IDMTapDetectingImageView.h" 12 | #import "IDMTapDetectingView.h" 13 | 14 | #import 15 | 16 | @class IDMPhotoBrowser, IDMPhoto, IDMCaptionView; 17 | 18 | @interface IDMZoomingScrollView : UIScrollView { 19 | 20 | IDMPhotoBrowser *__weak _photoBrowser; 21 | id _photo; 22 | 23 | // This view references the related caption view for simplified handling in photo browser 24 | IDMCaptionView *_captionView; 25 | 26 | IDMTapDetectingView *_tapView; // for background taps 27 | 28 | DACircularProgressView *_progressView; 29 | } 30 | 31 | @property (nonatomic, strong) IDMTapDetectingImageView *photoImageView; 32 | @property (nonatomic, strong) IDMCaptionView *captionView; 33 | @property (nonatomic, strong) id photo; 34 | @property (nonatomic) CGFloat maximumDoubleTapZoomScale; 35 | 36 | - (id)initWithPhotoBrowser:(IDMPhotoBrowser *)browser; 37 | - (void)displayImage; 38 | - (void)displayImageFailure; 39 | - (void)setProgress:(CGFloat)progress forPhoto:(IDMPhoto*)photo; 40 | - (void)setMaxMinZoomScalesForCurrentBounds; 41 | - (void)prepareForReuse; 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /Classes/IDMZoomingScrollView.m: -------------------------------------------------------------------------------- 1 | // 2 | // IDMZoomingScrollView.m 3 | // IDMPhotoBrowser 4 | // 5 | // Created by Michael Waterfall on 14/10/2010. 6 | // Copyright 2010 d3i. All rights reserved. 7 | // 8 | 9 | #import "IDMZoomingScrollView.h" 10 | #import "IDMPhotoBrowser.h" 11 | #import "IDMPhoto.h" 12 | 13 | // Declare private methods of browser 14 | @interface IDMPhotoBrowser () 15 | - (UIImage *)imageForPhoto:(id)photo; 16 | - (void)cancelControlHiding; 17 | - (void)hideControlsAfterDelay; 18 | //- (void)toggleControls; 19 | - (void)handleSingleTap; 20 | @end 21 | 22 | // Private methods and properties 23 | @interface IDMZoomingScrollView () 24 | @property (nonatomic, weak) IDMPhotoBrowser *photoBrowser; 25 | - (void)handleSingleTap:(CGPoint)touchPoint; 26 | - (void)handleDoubleTap:(CGPoint)touchPoint; 27 | @end 28 | 29 | @implementation IDMZoomingScrollView 30 | 31 | @synthesize photoImageView = _photoImageView, photoBrowser = _photoBrowser, photo = _photo, captionView = _captionView; 32 | 33 | - (id)initWithPhotoBrowser:(IDMPhotoBrowser *)browser { 34 | if ((self = [super init])) { 35 | // Delegate 36 | self.photoBrowser = browser; 37 | 38 | // Tap view for background 39 | _tapView = [[IDMTapDetectingView alloc] initWithFrame:self.bounds]; 40 | _tapView.tapDelegate = self; 41 | _tapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 42 | _tapView.backgroundColor = [UIColor clearColor]; 43 | [self addSubview:_tapView]; 44 | 45 | // Image view 46 | _photoImageView = [[IDMTapDetectingImageView alloc] initWithFrame:CGRectZero]; 47 | _photoImageView.tapDelegate = self; 48 | _photoImageView.backgroundColor = [UIColor clearColor]; 49 | if (@available(iOS 11.0, *)) { 50 | _photoImageView.accessibilityIgnoresInvertColors = YES; 51 | } else { 52 | // Fallback on earlier versions 53 | } 54 | [self addSubview:_photoImageView]; 55 | 56 | //Add darg&drop in iOS 11 57 | if (@available(iOS 11.0, *)) { 58 | UIDragInteraction *drag = [[UIDragInteraction alloc] initWithDelegate: self]; 59 | [_photoImageView addInteraction:drag]; 60 | } 61 | 62 | CGRect screenBound = [[UIScreen mainScreen] bounds]; 63 | CGFloat screenWidth = screenBound.size.width; 64 | CGFloat screenHeight = screenBound.size.height; 65 | 66 | if ([[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationLandscapeLeft || [[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationLandscapeRight) { 67 | screenWidth = screenBound.size.height; 68 | screenHeight = screenBound.size.width; 69 | } 70 | 71 | // Progress view 72 | _progressView = [[DACircularProgressView alloc] initWithFrame:CGRectMake((screenWidth-35.)/2., (screenHeight-35.)/2, 35.0f, 35.0f)]; 73 | [_progressView setProgress:0.0f]; 74 | _progressView.tag = 101; 75 | _progressView.thicknessRatio = 0.1; 76 | _progressView.roundedCorners = NO; 77 | _progressView.trackTintColor = browser.trackTintColor ? self.photoBrowser.trackTintColor : [UIColor colorWithWhite:0.2 alpha:1]; 78 | _progressView.progressTintColor = browser.progressTintColor ? self.photoBrowser.progressTintColor : [UIColor colorWithWhite:1.0 alpha:1]; 79 | [self addSubview:_progressView]; 80 | 81 | // Setup 82 | self.backgroundColor = [UIColor clearColor]; 83 | self.delegate = self; 84 | self.showsHorizontalScrollIndicator = NO; 85 | self.showsVerticalScrollIndicator = NO; 86 | self.decelerationRate = UIScrollViewDecelerationRateFast; 87 | self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 88 | } 89 | 90 | return self; 91 | } 92 | 93 | - (void)setPhoto:(id)photo { 94 | _photoImageView.image = nil; // Release image 95 | if (_photo != photo) { 96 | _photo = photo; 97 | } 98 | [self displayImage]; 99 | } 100 | 101 | - (void)prepareForReuse { 102 | self.photo = nil; 103 | [_captionView removeFromSuperview]; 104 | self.captionView = nil; 105 | } 106 | 107 | #pragma mark - Drag & Drop 108 | 109 | - (NSArray *)dragInteraction:(UIDragInteraction *)interaction itemsForBeginningSession:(id)session NS_AVAILABLE_IOS(11.0) { 110 | return @[[[UIDragItem alloc] initWithItemProvider:[[NSItemProvider alloc] initWithObject:_photoImageView.image]]]; 111 | } 112 | 113 | #pragma mark - Image 114 | 115 | // Get and display image 116 | - (void)displayImage { 117 | if (_photo) { 118 | // Reset 119 | self.maximumZoomScale = 1; 120 | self.minimumZoomScale = 1; 121 | self.zoomScale = 1; 122 | 123 | self.contentSize = CGSizeMake(0, 0); 124 | 125 | // Get image from browser as it handles ordering of fetching 126 | UIImage *img = [self.photoBrowser imageForPhoto:_photo]; 127 | if (img) { 128 | // Hide ProgressView 129 | //_progressView.alpha = 0.0f; 130 | [_progressView removeFromSuperview]; 131 | 132 | // Set image 133 | _photoImageView.image = img; 134 | _photoImageView.hidden = NO; 135 | 136 | // Setup photo frame 137 | CGRect photoImageViewFrame; 138 | photoImageViewFrame.origin = CGPointZero; 139 | photoImageViewFrame.size = img.size; 140 | 141 | _photoImageView.frame = photoImageViewFrame; 142 | self.contentSize = photoImageViewFrame.size; 143 | 144 | // Set zoom to minimum zoom 145 | [self setMaxMinZoomScalesForCurrentBounds]; 146 | } else { 147 | // Hide image view 148 | _photoImageView.hidden = YES; 149 | 150 | _progressView.alpha = 1.0f; 151 | } 152 | 153 | [self setNeedsLayout]; 154 | } 155 | } 156 | 157 | - (void)setProgress:(CGFloat)progress forPhoto:(IDMPhoto*)photo { 158 | IDMPhoto *p = (IDMPhoto*)self.photo; 159 | 160 | if ([photo.photoURL.absoluteString isEqualToString:p.photoURL.absoluteString]) { 161 | if (_progressView.progress < progress) { 162 | [_progressView setProgress:progress animated:YES]; 163 | } 164 | } 165 | } 166 | 167 | // Image failed so just show black! 168 | - (void)displayImageFailure { 169 | [_progressView removeFromSuperview]; 170 | } 171 | 172 | #pragma mark - Setup 173 | 174 | - (void)setMaxMinZoomScalesForCurrentBounds { 175 | // Reset 176 | self.maximumZoomScale = 1; 177 | self.minimumZoomScale = 1; 178 | self.zoomScale = 1; 179 | 180 | // Bail 181 | if (_photoImageView.image == nil) return; 182 | 183 | // Sizes 184 | CGSize boundsSize = self.bounds.size; 185 | boundsSize.width -= 0.1; 186 | boundsSize.height -= 0.1; 187 | 188 | CGSize imageSize = _photoImageView.frame.size; 189 | 190 | // Calculate Min 191 | CGFloat xScale = boundsSize.width / imageSize.width; // the scale needed to perfectly fit the image width-wise 192 | CGFloat yScale = boundsSize.height / imageSize.height; // the scale needed to perfectly fit the image height-wise 193 | CGFloat minScale = MIN(xScale, yScale); // use minimum of these to allow the image to become fully visible 194 | 195 | // If image is smaller than the screen then ensure we show it at 196 | // min scale of 1 197 | if (xScale > 1 && yScale > 1) { 198 | //minScale = 1.0; 199 | } 200 | 201 | // Calculate Max 202 | CGFloat maxScale = 4.0; // Allow double scale 203 | // on high resolution screens we have double the pixel density, so we will be seeing every pixel if we limit the 204 | // maximum zoom scale to 0.5. 205 | if ([UIScreen instancesRespondToSelector:@selector(scale)]) { 206 | maxScale = maxScale / [[UIScreen mainScreen] scale]; 207 | 208 | if (maxScale < minScale) { 209 | maxScale = minScale * 2; 210 | } 211 | } 212 | 213 | // Calculate Max Scale Of Double Tap 214 | CGFloat maxDoubleTapZoomScale = 4.0 * minScale; // Allow double scale 215 | // on high resolution screens we have double the pixel density, so we will be seeing every pixel if we limit the 216 | // maximum zoom scale to 0.5. 217 | if ([UIScreen instancesRespondToSelector:@selector(scale)]) { 218 | maxDoubleTapZoomScale = maxDoubleTapZoomScale / [[UIScreen mainScreen] scale]; 219 | 220 | if (maxDoubleTapZoomScale < minScale) { 221 | maxDoubleTapZoomScale = minScale * 2; 222 | } 223 | } 224 | 225 | // Make sure maxDoubleTapZoomScale isn't larger than maxScale 226 | maxDoubleTapZoomScale = MIN(maxDoubleTapZoomScale, maxScale); 227 | 228 | // Set 229 | self.maximumZoomScale = maxScale; 230 | self.minimumZoomScale = minScale; 231 | self.zoomScale = minScale; 232 | self.maximumDoubleTapZoomScale = maxDoubleTapZoomScale; 233 | 234 | // Reset position 235 | _photoImageView.frame = CGRectMake(0, 0, _photoImageView.frame.size.width, _photoImageView.frame.size.height); 236 | [self setNeedsLayout]; 237 | } 238 | 239 | #pragma mark - Layout 240 | 241 | - (void)layoutSubviews { 242 | // Update tap view frame 243 | _tapView.frame = self.bounds; 244 | 245 | // Super 246 | [super layoutSubviews]; 247 | 248 | // Center the image as it becomes smaller than the size of the screen 249 | CGSize boundsSize = self.bounds.size; 250 | CGRect frameToCenter = _photoImageView.frame; 251 | 252 | // Horizontally 253 | if (frameToCenter.size.width < boundsSize.width) { 254 | frameToCenter.origin.x = floorf((boundsSize.width - frameToCenter.size.width) / 2.0); 255 | } else { 256 | frameToCenter.origin.x = 0; 257 | } 258 | 259 | // Vertically 260 | if (frameToCenter.size.height < boundsSize.height) { 261 | frameToCenter.origin.y = floorf((boundsSize.height - frameToCenter.size.height) / 2.0); 262 | } else { 263 | frameToCenter.origin.y = 0; 264 | } 265 | 266 | // Center 267 | if (!CGRectEqualToRect(_photoImageView.frame, frameToCenter)) 268 | _photoImageView.frame = frameToCenter; 269 | } 270 | 271 | #pragma mark - UIScrollViewDelegate 272 | 273 | - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView { 274 | return _photoImageView; 275 | } 276 | 277 | - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { 278 | [_photoBrowser cancelControlHiding]; 279 | } 280 | 281 | - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view { 282 | [_photoBrowser cancelControlHiding]; 283 | } 284 | 285 | - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { 286 | [_photoBrowser hideControlsAfterDelay]; 287 | } 288 | 289 | - (void)scrollViewDidZoom:(UIScrollView *)scrollView { 290 | [self setNeedsLayout]; 291 | [self layoutIfNeeded]; 292 | } 293 | 294 | #pragma mark - Tap Detection 295 | 296 | - (void)handleSingleTap:(CGPoint)touchPoint { 297 | // [_photoBrowser performSelector:@selector(toggleControls) withObject:nil afterDelay:0.2]; 298 | [_photoBrowser performSelector:@selector(handleSingleTap) withObject:nil afterDelay:0.2]; 299 | } 300 | 301 | - (void)handleDoubleTap:(CGPoint)touchPoint { 302 | 303 | // Cancel any single tap handling 304 | [NSObject cancelPreviousPerformRequestsWithTarget:_photoBrowser]; 305 | 306 | // Zoom 307 | if (self.zoomScale == self.maximumDoubleTapZoomScale) { 308 | 309 | // Zoom out 310 | [self setZoomScale:self.minimumZoomScale animated:YES]; 311 | 312 | } else { 313 | 314 | // Zoom in 315 | CGSize targetSize = CGSizeMake(self.frame.size.width / self.maximumDoubleTapZoomScale, self.frame.size.height / self.maximumDoubleTapZoomScale); 316 | CGPoint targetPoint = CGPointMake(touchPoint.x - targetSize.width / 2, touchPoint.y - targetSize.height / 2); 317 | 318 | [self zoomToRect:CGRectMake(targetPoint.x, targetPoint.y, targetSize.width, targetSize.height) animated:YES]; 319 | 320 | } 321 | 322 | // Delay controls 323 | [_photoBrowser hideControlsAfterDelay]; 324 | } 325 | 326 | // Image View 327 | - (void)imageView:(UIImageView *)imageView singleTapDetected:(UITouch *)touch { 328 | [self handleSingleTap:[touch locationInView:imageView]]; 329 | } 330 | - (void)imageView:(UIImageView *)imageView doubleTapDetected:(UITouch *)touch { 331 | [self handleDoubleTap:[touch locationInView:imageView]]; 332 | } 333 | 334 | // Background View 335 | - (void)view:(UIView *)view singleTapDetected:(UITouch *)touch { 336 | [self handleSingleTap:[touch locationInView:view]]; 337 | } 338 | - (void)view:(UIView *)view doubleTapDetected:(UITouch *)touch { 339 | [self handleDoubleTap:[touch locationInView:view]]; 340 | } 341 | 342 | @end 343 | -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4C6F979214AF734900F8389A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C6F979114AF734900F8389A /* UIKit.framework */; }; 11 | 4C6F979414AF734900F8389A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C6F979314AF734900F8389A /* Foundation.framework */; }; 12 | 4C6F979614AF734900F8389A /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C6F979514AF734900F8389A /* CoreGraphics.framework */; }; 13 | 4C6F97C914AF750400F8389A /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C6F97C814AF750400F8389A /* MessageUI.framework */; }; 14 | 4C6F97D314AF760500F8389A /* photo1l.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 4C6F97CB14AF760500F8389A /* photo1l.jpg */; }; 15 | 4C6F97D414AF760500F8389A /* photo1m.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 4C6F97CC14AF760500F8389A /* photo1m.jpg */; }; 16 | 4C6F97D514AF760500F8389A /* photo2l.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 4C6F97CD14AF760500F8389A /* photo2l.jpg */; }; 17 | 4C6F97D614AF760500F8389A /* photo2m.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 4C6F97CE14AF760500F8389A /* photo2m.jpg */; }; 18 | 4C6F97D714AF760500F8389A /* photo3l.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 4C6F97CF14AF760500F8389A /* photo3l.jpg */; }; 19 | 4C6F97D814AF760500F8389A /* photo3m.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 4C6F97D014AF760500F8389A /* photo3m.jpg */; }; 20 | 4C6F97D914AF760500F8389A /* photo4l.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 4C6F97D114AF760500F8389A /* photo4l.jpg */; }; 21 | 4C6F97DA14AF760500F8389A /* photo4m.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 4C6F97D214AF760500F8389A /* photo4m.jpg */; }; 22 | 5736E0DD544CC84515494E76 /* libPods-PhotoBrowserDemo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64F5714840012BFE47F8331B /* libPods-PhotoBrowserDemo.a */; }; 23 | 727988B1187B3B5400966C66 /* IDMPBLocalizations.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 727988B0187B3B5400966C66 /* IDMPBLocalizations.bundle */; }; 24 | 727988B7187B3CF100966C66 /* IDMPhotoBrowser.podspec in Resources */ = {isa = PBXBuildFile; fileRef = 727988B6187B3CF100966C66 /* IDMPhotoBrowser.podspec */; }; 25 | 7D2B79B81FD2501300F2094F /* IDMUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D2B79B71FD2501200F2094F /* IDMUtils.m */; }; 26 | 7D2B79C01FD25B7600F2094F /* PhotoBrowserDemoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D2B79BF1FD25B7600F2094F /* PhotoBrowserDemoTests.m */; }; 27 | 7D2B79C81FD25BF300F2094F /* IDMUtilsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D2B79C71FD25BF300F2094F /* IDMUtilsTest.m */; }; 28 | D114BB411A32269C00E677FE /* Launch Screen.xib in Resources */ = {isa = PBXBuildFile; fileRef = D114BB401A32269C00E677FE /* Launch Screen.xib */; }; 29 | D1574968178DB94900B0211A /* IDMCaptionView.m in Sources */ = {isa = PBXBuildFile; fileRef = D157494F178DB94900B0211A /* IDMCaptionView.m */; }; 30 | D1574969178DB94900B0211A /* IDMPhoto.m in Sources */ = {isa = PBXBuildFile; fileRef = D1574951178DB94900B0211A /* IDMPhoto.m */; }; 31 | D157496A178DB94900B0211A /* IDMPhotoBrowser.bundle in Resources */ = {isa = PBXBuildFile; fileRef = D1574952178DB94900B0211A /* IDMPhotoBrowser.bundle */; }; 32 | D157496B178DB94900B0211A /* IDMPhotoBrowser.m in Sources */ = {isa = PBXBuildFile; fileRef = D1574954178DB94900B0211A /* IDMPhotoBrowser.m */; }; 33 | D157496C178DB94900B0211A /* IDMTapDetectingImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = D1574957178DB94900B0211A /* IDMTapDetectingImageView.m */; }; 34 | D157496D178DB94900B0211A /* IDMTapDetectingView.m in Sources */ = {isa = PBXBuildFile; fileRef = D1574959178DB94900B0211A /* IDMTapDetectingView.m */; }; 35 | D157496E178DB94900B0211A /* IDMZoomingScrollView.m in Sources */ = {isa = PBXBuildFile; fileRef = D157495B178DB94900B0211A /* IDMZoomingScrollView.m */; }; 36 | D1574971178DB95300B0211A /* README.markdown in Resources */ = {isa = PBXBuildFile; fileRef = D157496F178DB95300B0211A /* README.markdown */; }; 37 | D1574972178DB95300B0211A /* LICENSE.txt in Resources */ = {isa = PBXBuildFile; fileRef = D1574970178DB95300B0211A /* LICENSE.txt */; }; 38 | D172B9391DF396E300F56D69 /* IDMPhotoBrowser_customArrowLeft.png in Resources */ = {isa = PBXBuildFile; fileRef = D172B92F1DF396E300F56D69 /* IDMPhotoBrowser_customArrowLeft.png */; }; 39 | D172B93A1DF396E300F56D69 /* IDMPhotoBrowser_customArrowLeft@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D172B9301DF396E300F56D69 /* IDMPhotoBrowser_customArrowLeft@2x.png */; }; 40 | D172B93B1DF396E300F56D69 /* IDMPhotoBrowser_customArrowLeftSelected.png in Resources */ = {isa = PBXBuildFile; fileRef = D172B9311DF396E300F56D69 /* IDMPhotoBrowser_customArrowLeftSelected.png */; }; 41 | D172B93C1DF396E300F56D69 /* IDMPhotoBrowser_customArrowLeftSelected@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D172B9321DF396E300F56D69 /* IDMPhotoBrowser_customArrowLeftSelected@2x.png */; }; 42 | D172B93D1DF396E300F56D69 /* IDMPhotoBrowser_customArrowRight.png in Resources */ = {isa = PBXBuildFile; fileRef = D172B9331DF396E300F56D69 /* IDMPhotoBrowser_customArrowRight.png */; }; 43 | D172B93E1DF396E300F56D69 /* IDMPhotoBrowser_customArrowRight@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D172B9341DF396E300F56D69 /* IDMPhotoBrowser_customArrowRight@2x.png */; }; 44 | D172B93F1DF396E300F56D69 /* IDMPhotoBrowser_customArrowRightSelected.png in Resources */ = {isa = PBXBuildFile; fileRef = D172B9351DF396E300F56D69 /* IDMPhotoBrowser_customArrowRightSelected.png */; }; 45 | D172B9401DF396E300F56D69 /* IDMPhotoBrowser_customArrowRightSelected@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D172B9361DF396E300F56D69 /* IDMPhotoBrowser_customArrowRightSelected@2x.png */; }; 46 | D172B9411DF396E300F56D69 /* IDMPhotoBrowser_customDoneButton.png in Resources */ = {isa = PBXBuildFile; fileRef = D172B9371DF396E300F56D69 /* IDMPhotoBrowser_customDoneButton.png */; }; 47 | D172B9421DF396E300F56D69 /* IDMPhotoBrowser_customDoneButton@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D172B9381DF396E300F56D69 /* IDMPhotoBrowser_customDoneButton@2x.png */; }; 48 | D17F79ED1AB7C29600A65B37 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D17F79EC1AB7C29600A65B37 /* Images.xcassets */; }; 49 | D1890F7217C66CA70065C101 /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D1890F7117C66CA70065C101 /* ImageIO.framework */; }; 50 | D1A193581758F37B00CE615F /* libz.1.2.5.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = D1A193561758F37B00CE615F /* libz.1.2.5.dylib */; }; 51 | D1A193591758F37B00CE615F /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = D1A193571758F37B00CE615F /* libz.dylib */; }; 52 | D1A1935B1758F42300CE615F /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D1A1935A1758F42300CE615F /* SystemConfiguration.framework */; }; 53 | D1A1935D1758F42A00CE615F /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D1A1935C1758F42A00CE615F /* MobileCoreServices.framework */; }; 54 | D1A9ED521DEB684B00C238DA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A9ED511DEB684B00C238DA /* AppDelegate.swift */; }; 55 | D1A9ED541DEB685F00C238DA /* MenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A9ED531DEB685F00C238DA /* MenuViewController.swift */; }; 56 | D1B43D551721876000D8B807 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D1B43D541721875F00D8B807 /* QuartzCore.framework */; }; 57 | D1FA3A911805C340000FFE18 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D1FA3A901805C340000FFE18 /* Security.framework */; }; 58 | /* End PBXBuildFile section */ 59 | 60 | /* Begin PBXContainerItemProxy section */ 61 | 7D2B79C21FD25B7600F2094F /* PBXContainerItemProxy */ = { 62 | isa = PBXContainerItemProxy; 63 | containerPortal = 4C6F978414AF734800F8389A /* Project object */; 64 | proxyType = 1; 65 | remoteGlobalIDString = 4C6F978C14AF734800F8389A; 66 | remoteInfo = PhotoBrowserDemo; 67 | }; 68 | /* End PBXContainerItemProxy section */ 69 | 70 | /* Begin PBXFileReference section */ 71 | 4C6F978D14AF734900F8389A /* PhotoBrowserDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PhotoBrowserDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 72 | 4C6F979114AF734900F8389A /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 73 | 4C6F979314AF734900F8389A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 74 | 4C6F979514AF734900F8389A /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 75 | 4C6F979914AF734900F8389A /* PhotoBrowserDemo-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "PhotoBrowserDemo-Info.plist"; sourceTree = ""; }; 76 | 4C6F979F14AF734900F8389A /* PhotoBrowserDemo-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "PhotoBrowserDemo-Prefix.pch"; sourceTree = ""; }; 77 | 4C6F97C814AF750400F8389A /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = System/Library/Frameworks/MessageUI.framework; sourceTree = SDKROOT; }; 78 | 4C6F97CB14AF760500F8389A /* photo1l.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; name = photo1l.jpg; path = Photos/photo1l.jpg; sourceTree = ""; }; 79 | 4C6F97CC14AF760500F8389A /* photo1m.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; name = photo1m.jpg; path = Photos/photo1m.jpg; sourceTree = ""; }; 80 | 4C6F97CD14AF760500F8389A /* photo2l.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; name = photo2l.jpg; path = Photos/photo2l.jpg; sourceTree = ""; }; 81 | 4C6F97CE14AF760500F8389A /* photo2m.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; name = photo2m.jpg; path = Photos/photo2m.jpg; sourceTree = ""; }; 82 | 4C6F97CF14AF760500F8389A /* photo3l.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; name = photo3l.jpg; path = Photos/photo3l.jpg; sourceTree = ""; }; 83 | 4C6F97D014AF760500F8389A /* photo3m.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; name = photo3m.jpg; path = Photos/photo3m.jpg; sourceTree = ""; }; 84 | 4C6F97D114AF760500F8389A /* photo4l.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; name = photo4l.jpg; path = Photos/photo4l.jpg; sourceTree = ""; }; 85 | 4C6F97D214AF760500F8389A /* photo4m.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; name = photo4m.jpg; path = Photos/photo4m.jpg; sourceTree = ""; }; 86 | 64F5714840012BFE47F8331B /* libPods-PhotoBrowserDemo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-PhotoBrowserDemo.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 87 | 727988B0187B3B5400966C66 /* IDMPBLocalizations.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = IDMPBLocalizations.bundle; sourceTree = ""; }; 88 | 727988B6187B3CF100966C66 /* IDMPhotoBrowser.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = IDMPhotoBrowser.podspec; path = ../../IDMPhotoBrowser.podspec; sourceTree = ""; }; 89 | 7D2B79B61FD2501200F2094F /* IDMUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IDMUtils.h; sourceTree = ""; }; 90 | 7D2B79B71FD2501200F2094F /* IDMUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = IDMUtils.m; sourceTree = ""; }; 91 | 7D2B79BD1FD25B7600F2094F /* PhotoBrowserDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PhotoBrowserDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 92 | 7D2B79BF1FD25B7600F2094F /* PhotoBrowserDemoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PhotoBrowserDemoTests.m; sourceTree = ""; }; 93 | 7D2B79C11FD25B7600F2094F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 94 | 7D2B79C71FD25BF300F2094F /* IDMUtilsTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = IDMUtilsTest.m; sourceTree = ""; }; 95 | 7D7D66691E09D6D400410A67 /* IDMBrowserDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IDMBrowserDelegate.h; sourceTree = ""; }; 96 | 7D7D666A1E09D6DA00410A67 /* IDMPhotoDataSource.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IDMPhotoDataSource.h; sourceTree = ""; }; 97 | D00FA320EEACA8C835D9D626 /* Pods-PhotoBrowserDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PhotoBrowserDemo.debug.xcconfig"; path = "Pods/Target Support Files/Pods-PhotoBrowserDemo/Pods-PhotoBrowserDemo.debug.xcconfig"; sourceTree = ""; }; 98 | D11464B61802FFC4005B7BC3 /* IDMPBConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IDMPBConstants.h; sourceTree = ""; }; 99 | D114BB401A32269C00E677FE /* Launch Screen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = "Launch Screen.xib"; sourceTree = ""; }; 100 | D157494E178DB94900B0211A /* IDMCaptionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IDMCaptionView.h; sourceTree = ""; }; 101 | D157494F178DB94900B0211A /* IDMCaptionView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IDMCaptionView.m; sourceTree = ""; }; 102 | D1574950178DB94900B0211A /* IDMPhoto.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IDMPhoto.h; sourceTree = ""; }; 103 | D1574951178DB94900B0211A /* IDMPhoto.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IDMPhoto.m; sourceTree = ""; }; 104 | D1574952178DB94900B0211A /* IDMPhotoBrowser.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = IDMPhotoBrowser.bundle; sourceTree = ""; }; 105 | D1574953178DB94900B0211A /* IDMPhotoBrowser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IDMPhotoBrowser.h; sourceTree = ""; }; 106 | D1574954178DB94900B0211A /* IDMPhotoBrowser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IDMPhotoBrowser.m; sourceTree = ""; }; 107 | D1574955178DB94900B0211A /* IDMPhotoProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IDMPhotoProtocol.h; sourceTree = ""; }; 108 | D1574956178DB94900B0211A /* IDMTapDetectingImageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IDMTapDetectingImageView.h; sourceTree = ""; }; 109 | D1574957178DB94900B0211A /* IDMTapDetectingImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IDMTapDetectingImageView.m; sourceTree = ""; }; 110 | D1574958178DB94900B0211A /* IDMTapDetectingView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IDMTapDetectingView.h; sourceTree = ""; }; 111 | D1574959178DB94900B0211A /* IDMTapDetectingView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IDMTapDetectingView.m; sourceTree = ""; }; 112 | D157495A178DB94900B0211A /* IDMZoomingScrollView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IDMZoomingScrollView.h; sourceTree = ""; }; 113 | D157495B178DB94900B0211A /* IDMZoomingScrollView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IDMZoomingScrollView.m; sourceTree = ""; }; 114 | D157496F178DB95300B0211A /* README.markdown */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = README.markdown; path = ../../README.markdown; sourceTree = ""; }; 115 | D1574970178DB95300B0211A /* LICENSE.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = LICENSE.txt; path = ../../LICENSE.txt; sourceTree = ""; }; 116 | D172B92F1DF396E300F56D69 /* IDMPhotoBrowser_customArrowLeft.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = IDMPhotoBrowser_customArrowLeft.png; path = "PhotoBrowserDemo/Custom Images/IDMPhotoBrowser_customArrowLeft.png"; sourceTree = SOURCE_ROOT; }; 117 | D172B9301DF396E300F56D69 /* IDMPhotoBrowser_customArrowLeft@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "IDMPhotoBrowser_customArrowLeft@2x.png"; path = "PhotoBrowserDemo/Custom Images/IDMPhotoBrowser_customArrowLeft@2x.png"; sourceTree = SOURCE_ROOT; }; 118 | D172B9311DF396E300F56D69 /* IDMPhotoBrowser_customArrowLeftSelected.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = IDMPhotoBrowser_customArrowLeftSelected.png; path = "PhotoBrowserDemo/Custom Images/IDMPhotoBrowser_customArrowLeftSelected.png"; sourceTree = SOURCE_ROOT; }; 119 | D172B9321DF396E300F56D69 /* IDMPhotoBrowser_customArrowLeftSelected@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "IDMPhotoBrowser_customArrowLeftSelected@2x.png"; path = "PhotoBrowserDemo/Custom Images/IDMPhotoBrowser_customArrowLeftSelected@2x.png"; sourceTree = SOURCE_ROOT; }; 120 | D172B9331DF396E300F56D69 /* IDMPhotoBrowser_customArrowRight.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = IDMPhotoBrowser_customArrowRight.png; path = "PhotoBrowserDemo/Custom Images/IDMPhotoBrowser_customArrowRight.png"; sourceTree = SOURCE_ROOT; }; 121 | D172B9341DF396E300F56D69 /* IDMPhotoBrowser_customArrowRight@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "IDMPhotoBrowser_customArrowRight@2x.png"; path = "PhotoBrowserDemo/Custom Images/IDMPhotoBrowser_customArrowRight@2x.png"; sourceTree = SOURCE_ROOT; }; 122 | D172B9351DF396E300F56D69 /* IDMPhotoBrowser_customArrowRightSelected.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = IDMPhotoBrowser_customArrowRightSelected.png; path = "PhotoBrowserDemo/Custom Images/IDMPhotoBrowser_customArrowRightSelected.png"; sourceTree = SOURCE_ROOT; }; 123 | D172B9361DF396E300F56D69 /* IDMPhotoBrowser_customArrowRightSelected@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "IDMPhotoBrowser_customArrowRightSelected@2x.png"; path = "PhotoBrowserDemo/Custom Images/IDMPhotoBrowser_customArrowRightSelected@2x.png"; sourceTree = SOURCE_ROOT; }; 124 | D172B9371DF396E300F56D69 /* IDMPhotoBrowser_customDoneButton.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = IDMPhotoBrowser_customDoneButton.png; path = "PhotoBrowserDemo/Custom Images/IDMPhotoBrowser_customDoneButton.png"; sourceTree = SOURCE_ROOT; }; 125 | D172B9381DF396E300F56D69 /* IDMPhotoBrowser_customDoneButton@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "IDMPhotoBrowser_customDoneButton@2x.png"; path = "PhotoBrowserDemo/Custom Images/IDMPhotoBrowser_customDoneButton@2x.png"; sourceTree = SOURCE_ROOT; }; 126 | D17F79EC1AB7C29600A65B37 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 127 | D1890F7117C66CA70065C101 /* ImageIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ImageIO.framework; path = System/Library/Frameworks/ImageIO.framework; sourceTree = SDKROOT; }; 128 | D1A193561758F37B00CE615F /* libz.1.2.5.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.1.2.5.dylib; path = usr/lib/libz.1.2.5.dylib; sourceTree = SDKROOT; }; 129 | D1A193571758F37B00CE615F /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; }; 130 | D1A1935A1758F42300CE615F /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; 131 | D1A1935C1758F42A00CE615F /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; 132 | D1A9ED501DEB684B00C238DA /* PhotoBrowserDemo-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "PhotoBrowserDemo-Bridging-Header.h"; sourceTree = ""; }; 133 | D1A9ED511DEB684B00C238DA /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 134 | D1A9ED531DEB685F00C238DA /* MenuViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuViewController.swift; sourceTree = ""; }; 135 | D1B43D541721875F00D8B807 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 136 | D1FA3A901805C340000FFE18 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 137 | DEF1811E2E5676DB0ECAA3AE /* Pods-PhotoBrowserDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PhotoBrowserDemo.release.xcconfig"; path = "Pods/Target Support Files/Pods-PhotoBrowserDemo/Pods-PhotoBrowserDemo.release.xcconfig"; sourceTree = ""; }; 138 | /* End PBXFileReference section */ 139 | 140 | /* Begin PBXFrameworksBuildPhase section */ 141 | 4C6F978A14AF734800F8389A /* Frameworks */ = { 142 | isa = PBXFrameworksBuildPhase; 143 | buildActionMask = 2147483647; 144 | files = ( 145 | D1FA3A911805C340000FFE18 /* Security.framework in Frameworks */, 146 | D1890F7217C66CA70065C101 /* ImageIO.framework in Frameworks */, 147 | D1A1935D1758F42A00CE615F /* MobileCoreServices.framework in Frameworks */, 148 | D1A1935B1758F42300CE615F /* SystemConfiguration.framework in Frameworks */, 149 | D1A193581758F37B00CE615F /* libz.1.2.5.dylib in Frameworks */, 150 | D1A193591758F37B00CE615F /* libz.dylib in Frameworks */, 151 | D1B43D551721876000D8B807 /* QuartzCore.framework in Frameworks */, 152 | 4C6F97C914AF750400F8389A /* MessageUI.framework in Frameworks */, 153 | 4C6F979214AF734900F8389A /* UIKit.framework in Frameworks */, 154 | 4C6F979414AF734900F8389A /* Foundation.framework in Frameworks */, 155 | 4C6F979614AF734900F8389A /* CoreGraphics.framework in Frameworks */, 156 | 5736E0DD544CC84515494E76 /* libPods-PhotoBrowserDemo.a in Frameworks */, 157 | ); 158 | runOnlyForDeploymentPostprocessing = 0; 159 | }; 160 | 7D2B79BA1FD25B7600F2094F /* Frameworks */ = { 161 | isa = PBXFrameworksBuildPhase; 162 | buildActionMask = 2147483647; 163 | files = ( 164 | ); 165 | runOnlyForDeploymentPostprocessing = 0; 166 | }; 167 | /* End PBXFrameworksBuildPhase section */ 168 | 169 | /* Begin PBXGroup section */ 170 | 4C6F978214AF734800F8389A = { 171 | isa = PBXGroup; 172 | children = ( 173 | D1574931178DB94900B0211A /* IDMPhotoBrowser */, 174 | 4C6F979714AF734900F8389A /* Demo */, 175 | 4C6F97CA14AF75D300F8389A /* Sample Photos */, 176 | D19D825517E8D40700A5859E /* Custom Images */, 177 | 4C6F979814AF734900F8389A /* Supporting Files */, 178 | 7D2B79BE1FD25B7600F2094F /* PhotoBrowserDemoTests */, 179 | 4C6F979014AF734900F8389A /* Frameworks */, 180 | 4C6F978E14AF734900F8389A /* Products */, 181 | 6D1B0039D6199AD450E98829 /* Pods */, 182 | ); 183 | sourceTree = ""; 184 | }; 185 | 4C6F978E14AF734900F8389A /* Products */ = { 186 | isa = PBXGroup; 187 | children = ( 188 | 4C6F978D14AF734900F8389A /* PhotoBrowserDemo.app */, 189 | 7D2B79BD1FD25B7600F2094F /* PhotoBrowserDemoTests.xctest */, 190 | ); 191 | name = Products; 192 | sourceTree = ""; 193 | }; 194 | 4C6F979014AF734900F8389A /* Frameworks */ = { 195 | isa = PBXGroup; 196 | children = ( 197 | D1A193561758F37B00CE615F /* libz.1.2.5.dylib */, 198 | D1A193571758F37B00CE615F /* libz.dylib */, 199 | 4C6F979514AF734900F8389A /* CoreGraphics.framework */, 200 | 4C6F979314AF734900F8389A /* Foundation.framework */, 201 | D1890F7117C66CA70065C101 /* ImageIO.framework */, 202 | 4C6F97C814AF750400F8389A /* MessageUI.framework */, 203 | D1A1935C1758F42A00CE615F /* MobileCoreServices.framework */, 204 | D1B43D541721875F00D8B807 /* QuartzCore.framework */, 205 | D1FA3A901805C340000FFE18 /* Security.framework */, 206 | D1A1935A1758F42300CE615F /* SystemConfiguration.framework */, 207 | 4C6F979114AF734900F8389A /* UIKit.framework */, 208 | 64F5714840012BFE47F8331B /* libPods-PhotoBrowserDemo.a */, 209 | ); 210 | name = Frameworks; 211 | sourceTree = ""; 212 | }; 213 | 4C6F979714AF734900F8389A /* Demo */ = { 214 | isa = PBXGroup; 215 | children = ( 216 | D1A9ED511DEB684B00C238DA /* AppDelegate.swift */, 217 | D1A9ED531DEB685F00C238DA /* MenuViewController.swift */, 218 | D17F79EC1AB7C29600A65B37 /* Images.xcassets */, 219 | ); 220 | name = Demo; 221 | path = PhotoBrowserDemo; 222 | sourceTree = ""; 223 | }; 224 | 4C6F979814AF734900F8389A /* Supporting Files */ = { 225 | isa = PBXGroup; 226 | children = ( 227 | 727988B6187B3CF100966C66 /* IDMPhotoBrowser.podspec */, 228 | D114BB401A32269C00E677FE /* Launch Screen.xib */, 229 | D1574970178DB95300B0211A /* LICENSE.txt */, 230 | D1A9ED501DEB684B00C238DA /* PhotoBrowserDemo-Bridging-Header.h */, 231 | 4C6F979914AF734900F8389A /* PhotoBrowserDemo-Info.plist */, 232 | 4C6F979F14AF734900F8389A /* PhotoBrowserDemo-Prefix.pch */, 233 | D157496F178DB95300B0211A /* README.markdown */, 234 | ); 235 | name = "Supporting Files"; 236 | path = PhotoBrowserDemo; 237 | sourceTree = ""; 238 | }; 239 | 4C6F97CA14AF75D300F8389A /* Sample Photos */ = { 240 | isa = PBXGroup; 241 | children = ( 242 | 4C6F97CB14AF760500F8389A /* photo1l.jpg */, 243 | 4C6F97CC14AF760500F8389A /* photo1m.jpg */, 244 | 4C6F97CD14AF760500F8389A /* photo2l.jpg */, 245 | 4C6F97CE14AF760500F8389A /* photo2m.jpg */, 246 | 4C6F97CF14AF760500F8389A /* photo3l.jpg */, 247 | 4C6F97D014AF760500F8389A /* photo3m.jpg */, 248 | 4C6F97D114AF760500F8389A /* photo4l.jpg */, 249 | 4C6F97D214AF760500F8389A /* photo4m.jpg */, 250 | ); 251 | name = "Sample Photos"; 252 | path = PhotoBrowserDemo; 253 | sourceTree = ""; 254 | }; 255 | 6D1B0039D6199AD450E98829 /* Pods */ = { 256 | isa = PBXGroup; 257 | children = ( 258 | D00FA320EEACA8C835D9D626 /* Pods-PhotoBrowserDemo.debug.xcconfig */, 259 | DEF1811E2E5676DB0ECAA3AE /* Pods-PhotoBrowserDemo.release.xcconfig */, 260 | ); 261 | name = Pods; 262 | sourceTree = ""; 263 | }; 264 | 7D2B79BE1FD25B7600F2094F /* PhotoBrowserDemoTests */ = { 265 | isa = PBXGroup; 266 | children = ( 267 | 7D2B79BF1FD25B7600F2094F /* PhotoBrowserDemoTests.m */, 268 | 7D2B79C71FD25BF300F2094F /* IDMUtilsTest.m */, 269 | 7D2B79C11FD25B7600F2094F /* Info.plist */, 270 | ); 271 | path = PhotoBrowserDemoTests; 272 | sourceTree = ""; 273 | }; 274 | D1574931178DB94900B0211A /* IDMPhotoBrowser */ = { 275 | isa = PBXGroup; 276 | children = ( 277 | D157494E178DB94900B0211A /* IDMCaptionView.h */, 278 | D157494F178DB94900B0211A /* IDMCaptionView.m */, 279 | D11464B61802FFC4005B7BC3 /* IDMPBConstants.h */, 280 | D1574950178DB94900B0211A /* IDMPhoto.h */, 281 | D1574951178DB94900B0211A /* IDMPhoto.m */, 282 | D1574953178DB94900B0211A /* IDMPhotoBrowser.h */, 283 | D1574954178DB94900B0211A /* IDMPhotoBrowser.m */, 284 | 7D2B79B61FD2501200F2094F /* IDMUtils.h */, 285 | 7D2B79B71FD2501200F2094F /* IDMUtils.m */, 286 | 7D7D666A1E09D6DA00410A67 /* IDMPhotoDataSource.h */, 287 | D1574955178DB94900B0211A /* IDMPhotoProtocol.h */, 288 | D1574956178DB94900B0211A /* IDMTapDetectingImageView.h */, 289 | D1574957178DB94900B0211A /* IDMTapDetectingImageView.m */, 290 | D1574958178DB94900B0211A /* IDMTapDetectingView.h */, 291 | D1574959178DB94900B0211A /* IDMTapDetectingView.m */, 292 | D157495A178DB94900B0211A /* IDMZoomingScrollView.h */, 293 | D157495B178DB94900B0211A /* IDMZoomingScrollView.m */, 294 | 727988B0187B3B5400966C66 /* IDMPBLocalizations.bundle */, 295 | D1574952178DB94900B0211A /* IDMPhotoBrowser.bundle */, 296 | ); 297 | name = IDMPhotoBrowser; 298 | path = ../Classes; 299 | sourceTree = ""; 300 | }; 301 | D19D825517E8D40700A5859E /* Custom Images */ = { 302 | isa = PBXGroup; 303 | children = ( 304 | D172B92F1DF396E300F56D69 /* IDMPhotoBrowser_customArrowLeft.png */, 305 | D172B9301DF396E300F56D69 /* IDMPhotoBrowser_customArrowLeft@2x.png */, 306 | D172B9311DF396E300F56D69 /* IDMPhotoBrowser_customArrowLeftSelected.png */, 307 | D172B9321DF396E300F56D69 /* IDMPhotoBrowser_customArrowLeftSelected@2x.png */, 308 | D172B9331DF396E300F56D69 /* IDMPhotoBrowser_customArrowRight.png */, 309 | D172B9341DF396E300F56D69 /* IDMPhotoBrowser_customArrowRight@2x.png */, 310 | D172B9351DF396E300F56D69 /* IDMPhotoBrowser_customArrowRightSelected.png */, 311 | D172B9361DF396E300F56D69 /* IDMPhotoBrowser_customArrowRightSelected@2x.png */, 312 | D172B9371DF396E300F56D69 /* IDMPhotoBrowser_customDoneButton.png */, 313 | D172B9381DF396E300F56D69 /* IDMPhotoBrowser_customDoneButton@2x.png */, 314 | ); 315 | name = "Custom Images"; 316 | path = PhotoBrowserDemo/CustomImages; 317 | sourceTree = ""; 318 | }; 319 | /* End PBXGroup section */ 320 | 321 | /* Begin PBXNativeTarget section */ 322 | 4C6F978C14AF734800F8389A /* PhotoBrowserDemo */ = { 323 | isa = PBXNativeTarget; 324 | buildConfigurationList = 4C6F97AE14AF734900F8389A /* Build configuration list for PBXNativeTarget "PhotoBrowserDemo" */; 325 | buildPhases = ( 326 | 9FEC6B6CD1E66BC894CF44FA /* [CP] Check Pods Manifest.lock */, 327 | 4C6F978914AF734800F8389A /* Sources */, 328 | 4C6F978A14AF734800F8389A /* Frameworks */, 329 | 4C6F978B14AF734800F8389A /* Resources */, 330 | 64D1E44DFBE2976FB9C75718 /* [CP] Embed Pods Frameworks */, 331 | F88445D630091F1AA9A2F89A /* [CP] Copy Pods Resources */, 332 | ); 333 | buildRules = ( 334 | ); 335 | dependencies = ( 336 | ); 337 | name = PhotoBrowserDemo; 338 | productName = PhotoBrowserDemo; 339 | productReference = 4C6F978D14AF734900F8389A /* PhotoBrowserDemo.app */; 340 | productType = "com.apple.product-type.application"; 341 | }; 342 | 7D2B79BC1FD25B7600F2094F /* PhotoBrowserDemoTests */ = { 343 | isa = PBXNativeTarget; 344 | buildConfigurationList = 7D2B79C41FD25B7600F2094F /* Build configuration list for PBXNativeTarget "PhotoBrowserDemoTests" */; 345 | buildPhases = ( 346 | 7D2B79B91FD25B7600F2094F /* Sources */, 347 | 7D2B79BA1FD25B7600F2094F /* Frameworks */, 348 | 7D2B79BB1FD25B7600F2094F /* Resources */, 349 | ); 350 | buildRules = ( 351 | ); 352 | dependencies = ( 353 | 7D2B79C31FD25B7600F2094F /* PBXTargetDependency */, 354 | ); 355 | name = PhotoBrowserDemoTests; 356 | productName = PhotoBrowserDemoTests; 357 | productReference = 7D2B79BD1FD25B7600F2094F /* PhotoBrowserDemoTests.xctest */; 358 | productType = "com.apple.product-type.bundle.unit-test"; 359 | }; 360 | /* End PBXNativeTarget section */ 361 | 362 | /* Begin PBXProject section */ 363 | 4C6F978414AF734800F8389A /* Project object */ = { 364 | isa = PBXProject; 365 | attributes = { 366 | LastUpgradeCheck = 0800; 367 | TargetAttributes = { 368 | 4C6F978C14AF734800F8389A = { 369 | LastSwiftMigration = 0810; 370 | }; 371 | 7D2B79BC1FD25B7600F2094F = { 372 | CreatedOnToolsVersion = 9.1; 373 | ProvisioningStyle = Automatic; 374 | TestTargetID = 4C6F978C14AF734800F8389A; 375 | }; 376 | }; 377 | }; 378 | buildConfigurationList = 4C6F978714AF734800F8389A /* Build configuration list for PBXProject "PhotoBrowserDemo" */; 379 | compatibilityVersion = "Xcode 3.2"; 380 | developmentRegion = English; 381 | hasScannedForEncodings = 0; 382 | knownRegions = ( 383 | en, 384 | fr, 385 | pt, 386 | ); 387 | mainGroup = 4C6F978214AF734800F8389A; 388 | productRefGroup = 4C6F978E14AF734900F8389A /* Products */; 389 | projectDirPath = ""; 390 | projectRoot = ""; 391 | targets = ( 392 | 4C6F978C14AF734800F8389A /* PhotoBrowserDemo */, 393 | 7D2B79BC1FD25B7600F2094F /* PhotoBrowserDemoTests */, 394 | ); 395 | }; 396 | /* End PBXProject section */ 397 | 398 | /* Begin PBXResourcesBuildPhase section */ 399 | 4C6F978B14AF734800F8389A /* Resources */ = { 400 | isa = PBXResourcesBuildPhase; 401 | buildActionMask = 2147483647; 402 | files = ( 403 | D172B93C1DF396E300F56D69 /* IDMPhotoBrowser_customArrowLeftSelected@2x.png in Resources */, 404 | 4C6F97D314AF760500F8389A /* photo1l.jpg in Resources */, 405 | D17F79ED1AB7C29600A65B37 /* Images.xcassets in Resources */, 406 | D172B9411DF396E300F56D69 /* IDMPhotoBrowser_customDoneButton.png in Resources */, 407 | 4C6F97D414AF760500F8389A /* photo1m.jpg in Resources */, 408 | 4C6F97D514AF760500F8389A /* photo2l.jpg in Resources */, 409 | 4C6F97D614AF760500F8389A /* photo2m.jpg in Resources */, 410 | 4C6F97D714AF760500F8389A /* photo3l.jpg in Resources */, 411 | 4C6F97D814AF760500F8389A /* photo3m.jpg in Resources */, 412 | D172B93B1DF396E300F56D69 /* IDMPhotoBrowser_customArrowLeftSelected.png in Resources */, 413 | 4C6F97D914AF760500F8389A /* photo4l.jpg in Resources */, 414 | D172B93E1DF396E300F56D69 /* IDMPhotoBrowser_customArrowRight@2x.png in Resources */, 415 | 4C6F97DA14AF760500F8389A /* photo4m.jpg in Resources */, 416 | D157496A178DB94900B0211A /* IDMPhotoBrowser.bundle in Resources */, 417 | D172B93A1DF396E300F56D69 /* IDMPhotoBrowser_customArrowLeft@2x.png in Resources */, 418 | D1574971178DB95300B0211A /* README.markdown in Resources */, 419 | D1574972178DB95300B0211A /* LICENSE.txt in Resources */, 420 | D172B9421DF396E300F56D69 /* IDMPhotoBrowser_customDoneButton@2x.png in Resources */, 421 | D114BB411A32269C00E677FE /* Launch Screen.xib in Resources */, 422 | 727988B1187B3B5400966C66 /* IDMPBLocalizations.bundle in Resources */, 423 | D172B9391DF396E300F56D69 /* IDMPhotoBrowser_customArrowLeft.png in Resources */, 424 | D172B93D1DF396E300F56D69 /* IDMPhotoBrowser_customArrowRight.png in Resources */, 425 | 727988B7187B3CF100966C66 /* IDMPhotoBrowser.podspec in Resources */, 426 | D172B93F1DF396E300F56D69 /* IDMPhotoBrowser_customArrowRightSelected.png in Resources */, 427 | D172B9401DF396E300F56D69 /* IDMPhotoBrowser_customArrowRightSelected@2x.png in Resources */, 428 | ); 429 | runOnlyForDeploymentPostprocessing = 0; 430 | }; 431 | 7D2B79BB1FD25B7600F2094F /* Resources */ = { 432 | isa = PBXResourcesBuildPhase; 433 | buildActionMask = 2147483647; 434 | files = ( 435 | ); 436 | runOnlyForDeploymentPostprocessing = 0; 437 | }; 438 | /* End PBXResourcesBuildPhase section */ 439 | 440 | /* Begin PBXShellScriptBuildPhase section */ 441 | 64D1E44DFBE2976FB9C75718 /* [CP] Embed Pods Frameworks */ = { 442 | isa = PBXShellScriptBuildPhase; 443 | buildActionMask = 2147483647; 444 | files = ( 445 | ); 446 | inputPaths = ( 447 | ); 448 | name = "[CP] Embed Pods Frameworks"; 449 | outputPaths = ( 450 | ); 451 | runOnlyForDeploymentPostprocessing = 0; 452 | shellPath = /bin/sh; 453 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-PhotoBrowserDemo/Pods-PhotoBrowserDemo-frameworks.sh\"\n"; 454 | showEnvVarsInLog = 0; 455 | }; 456 | 9FEC6B6CD1E66BC894CF44FA /* [CP] Check Pods Manifest.lock */ = { 457 | isa = PBXShellScriptBuildPhase; 458 | buildActionMask = 2147483647; 459 | files = ( 460 | ); 461 | inputPaths = ( 462 | ); 463 | name = "[CP] Check Pods Manifest.lock"; 464 | outputPaths = ( 465 | ); 466 | runOnlyForDeploymentPostprocessing = 0; 467 | shellPath = /bin/sh; 468 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; 469 | showEnvVarsInLog = 0; 470 | }; 471 | F88445D630091F1AA9A2F89A /* [CP] Copy Pods Resources */ = { 472 | isa = PBXShellScriptBuildPhase; 473 | buildActionMask = 2147483647; 474 | files = ( 475 | ); 476 | inputPaths = ( 477 | ); 478 | name = "[CP] Copy Pods Resources"; 479 | outputPaths = ( 480 | ); 481 | runOnlyForDeploymentPostprocessing = 0; 482 | shellPath = /bin/sh; 483 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-PhotoBrowserDemo/Pods-PhotoBrowserDemo-resources.sh\"\n"; 484 | showEnvVarsInLog = 0; 485 | }; 486 | /* End PBXShellScriptBuildPhase section */ 487 | 488 | /* Begin PBXSourcesBuildPhase section */ 489 | 4C6F978914AF734800F8389A /* Sources */ = { 490 | isa = PBXSourcesBuildPhase; 491 | buildActionMask = 2147483647; 492 | files = ( 493 | 7D2B79B81FD2501300F2094F /* IDMUtils.m in Sources */, 494 | D1574968178DB94900B0211A /* IDMCaptionView.m in Sources */, 495 | D1A9ED541DEB685F00C238DA /* MenuViewController.swift in Sources */, 496 | D1574969178DB94900B0211A /* IDMPhoto.m in Sources */, 497 | D157496B178DB94900B0211A /* IDMPhotoBrowser.m in Sources */, 498 | D157496C178DB94900B0211A /* IDMTapDetectingImageView.m in Sources */, 499 | D157496D178DB94900B0211A /* IDMTapDetectingView.m in Sources */, 500 | D1A9ED521DEB684B00C238DA /* AppDelegate.swift in Sources */, 501 | D157496E178DB94900B0211A /* IDMZoomingScrollView.m in Sources */, 502 | ); 503 | runOnlyForDeploymentPostprocessing = 0; 504 | }; 505 | 7D2B79B91FD25B7600F2094F /* Sources */ = { 506 | isa = PBXSourcesBuildPhase; 507 | buildActionMask = 2147483647; 508 | files = ( 509 | 7D2B79C81FD25BF300F2094F /* IDMUtilsTest.m in Sources */, 510 | 7D2B79C01FD25B7600F2094F /* PhotoBrowserDemoTests.m in Sources */, 511 | ); 512 | runOnlyForDeploymentPostprocessing = 0; 513 | }; 514 | /* End PBXSourcesBuildPhase section */ 515 | 516 | /* Begin PBXTargetDependency section */ 517 | 7D2B79C31FD25B7600F2094F /* PBXTargetDependency */ = { 518 | isa = PBXTargetDependency; 519 | target = 4C6F978C14AF734800F8389A /* PhotoBrowserDemo */; 520 | targetProxy = 7D2B79C21FD25B7600F2094F /* PBXContainerItemProxy */; 521 | }; 522 | /* End PBXTargetDependency section */ 523 | 524 | /* Begin XCBuildConfiguration section */ 525 | 4C6F97AC14AF734900F8389A /* Debug */ = { 526 | isa = XCBuildConfiguration; 527 | buildSettings = { 528 | ALWAYS_SEARCH_USER_PATHS = YES; 529 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 530 | CLANG_WARN_BOOL_CONVERSION = YES; 531 | CLANG_WARN_CONSTANT_CONVERSION = YES; 532 | CLANG_WARN_EMPTY_BODY = YES; 533 | CLANG_WARN_ENUM_CONVERSION = YES; 534 | CLANG_WARN_INFINITE_RECURSION = YES; 535 | CLANG_WARN_INT_CONVERSION = YES; 536 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 537 | CLANG_WARN_UNREACHABLE_CODE = YES; 538 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 539 | CODE_SIGN_IDENTITY = ""; 540 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 541 | COPY_PHASE_STRIP = NO; 542 | ENABLE_STRICT_OBJC_MSGSEND = YES; 543 | ENABLE_TESTABILITY = YES; 544 | GCC_C_LANGUAGE_STANDARD = gnu99; 545 | GCC_DYNAMIC_NO_PIC = NO; 546 | GCC_NO_COMMON_BLOCKS = YES; 547 | GCC_OPTIMIZATION_LEVEL = 0; 548 | GCC_PREPROCESSOR_DEFINITIONS = ( 549 | "DEBUG=1", 550 | "$(inherited)", 551 | ); 552 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 553 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 554 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 555 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 556 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 557 | GCC_WARN_UNDECLARED_SELECTOR = YES; 558 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 559 | GCC_WARN_UNUSED_FUNCTION = YES; 560 | GCC_WARN_UNUSED_VARIABLE = YES; 561 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 562 | ONLY_ACTIVE_ARCH = YES; 563 | SDKROOT = iphoneos; 564 | SWIFT_VERSION = 3.0.1; 565 | TARGETED_DEVICE_FAMILY = "1,2"; 566 | }; 567 | name = Debug; 568 | }; 569 | 4C6F97AD14AF734900F8389A /* Release */ = { 570 | isa = XCBuildConfiguration; 571 | buildSettings = { 572 | ALWAYS_SEARCH_USER_PATHS = YES; 573 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 574 | CLANG_WARN_BOOL_CONVERSION = YES; 575 | CLANG_WARN_CONSTANT_CONVERSION = YES; 576 | CLANG_WARN_EMPTY_BODY = YES; 577 | CLANG_WARN_ENUM_CONVERSION = YES; 578 | CLANG_WARN_INFINITE_RECURSION = YES; 579 | CLANG_WARN_INT_CONVERSION = YES; 580 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 581 | CLANG_WARN_UNREACHABLE_CODE = YES; 582 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 583 | CODE_SIGN_IDENTITY = ""; 584 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 585 | COPY_PHASE_STRIP = YES; 586 | ENABLE_STRICT_OBJC_MSGSEND = YES; 587 | GCC_C_LANGUAGE_STANDARD = gnu99; 588 | GCC_NO_COMMON_BLOCKS = YES; 589 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 590 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 591 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 592 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 593 | GCC_WARN_UNDECLARED_SELECTOR = YES; 594 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 595 | GCC_WARN_UNUSED_FUNCTION = YES; 596 | GCC_WARN_UNUSED_VARIABLE = YES; 597 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 598 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 599 | SDKROOT = iphoneos; 600 | SWIFT_VERSION = 3.0.1; 601 | TARGETED_DEVICE_FAMILY = "1,2"; 602 | VALIDATE_PRODUCT = YES; 603 | }; 604 | name = Release; 605 | }; 606 | 4C6F97AF14AF734900F8389A /* Debug */ = { 607 | isa = XCBuildConfiguration; 608 | baseConfigurationReference = D00FA320EEACA8C835D9D626 /* Pods-PhotoBrowserDemo.debug.xcconfig */; 609 | buildSettings = { 610 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 611 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 612 | CLANG_ENABLE_MODULES = YES; 613 | CLANG_ENABLE_OBJC_ARC = YES; 614 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 615 | GCC_PREFIX_HEADER = "PhotoBrowserDemo/PhotoBrowserDemo-Prefix.pch"; 616 | GCC_THUMB_SUPPORT = NO; 617 | INFOPLIST_FILE = "PhotoBrowserDemo/PhotoBrowserDemo-Info.plist"; 618 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 619 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 620 | PRODUCT_BUNDLE_IDENTIFIER = "com.appkraft.${PRODUCT_NAME:rfc1034identifier}"; 621 | PRODUCT_NAME = "$(TARGET_NAME)"; 622 | SWIFT_OBJC_BRIDGING_HEADER = "PhotoBrowserDemo/PhotoBrowserDemo-Bridging-Header.h"; 623 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 624 | SWIFT_VERSION = 3.0.1; 625 | TARGETED_DEVICE_FAMILY = "1,2"; 626 | USER_HEADER_SEARCH_PATHS = "../**"; 627 | WRAPPER_EXTENSION = app; 628 | }; 629 | name = Debug; 630 | }; 631 | 4C6F97B014AF734900F8389A /* Release */ = { 632 | isa = XCBuildConfiguration; 633 | baseConfigurationReference = DEF1811E2E5676DB0ECAA3AE /* Pods-PhotoBrowserDemo.release.xcconfig */; 634 | buildSettings = { 635 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 636 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 637 | CLANG_ENABLE_MODULES = YES; 638 | CLANG_ENABLE_OBJC_ARC = YES; 639 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 640 | GCC_PREFIX_HEADER = "PhotoBrowserDemo/PhotoBrowserDemo-Prefix.pch"; 641 | GCC_THUMB_SUPPORT = NO; 642 | INFOPLIST_FILE = "PhotoBrowserDemo/PhotoBrowserDemo-Info.plist"; 643 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 644 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 645 | PRODUCT_BUNDLE_IDENTIFIER = "com.appkraft.${PRODUCT_NAME:rfc1034identifier}"; 646 | PRODUCT_NAME = "$(TARGET_NAME)"; 647 | SWIFT_OBJC_BRIDGING_HEADER = "PhotoBrowserDemo/PhotoBrowserDemo-Bridging-Header.h"; 648 | SWIFT_VERSION = 3.0.1; 649 | TARGETED_DEVICE_FAMILY = "1,2"; 650 | USER_HEADER_SEARCH_PATHS = "../**"; 651 | WRAPPER_EXTENSION = app; 652 | }; 653 | name = Release; 654 | }; 655 | 7D2B79C51FD25B7600F2094F /* Debug */ = { 656 | isa = XCBuildConfiguration; 657 | buildSettings = { 658 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 659 | ALWAYS_SEARCH_USER_PATHS = NO; 660 | BUNDLE_LOADER = "$(TEST_HOST)"; 661 | CLANG_ANALYZER_NONNULL = YES; 662 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 663 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 664 | CLANG_CXX_LIBRARY = "libc++"; 665 | CLANG_ENABLE_MODULES = YES; 666 | CLANG_ENABLE_OBJC_ARC = YES; 667 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 668 | CLANG_WARN_COMMA = YES; 669 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 670 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 671 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 672 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 673 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 674 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 675 | CLANG_WARN_STRICT_PROTOTYPES = YES; 676 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 677 | CODE_SIGN_IDENTITY = "iPhone Developer"; 678 | CODE_SIGN_STYLE = Automatic; 679 | DEBUG_INFORMATION_FORMAT = dwarf; 680 | GCC_C_LANGUAGE_STANDARD = gnu11; 681 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 682 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 683 | INFOPLIST_FILE = PhotoBrowserDemoTests/Info.plist; 684 | IPHONEOS_DEPLOYMENT_TARGET = 11.1; 685 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 686 | MTL_ENABLE_DEBUG_INFO = YES; 687 | PRODUCT_BUNDLE_IDENTIFIER = com.appkraft.PhotoBrowserDemoTests; 688 | PRODUCT_NAME = "$(TARGET_NAME)"; 689 | TARGETED_DEVICE_FAMILY = "1,2"; 690 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PhotoBrowserDemo.app/PhotoBrowserDemo"; 691 | }; 692 | name = Debug; 693 | }; 694 | 7D2B79C61FD25B7600F2094F /* Release */ = { 695 | isa = XCBuildConfiguration; 696 | buildSettings = { 697 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 698 | ALWAYS_SEARCH_USER_PATHS = NO; 699 | BUNDLE_LOADER = "$(TEST_HOST)"; 700 | CLANG_ANALYZER_NONNULL = YES; 701 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 702 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 703 | CLANG_CXX_LIBRARY = "libc++"; 704 | CLANG_ENABLE_MODULES = YES; 705 | CLANG_ENABLE_OBJC_ARC = YES; 706 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 707 | CLANG_WARN_COMMA = YES; 708 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 709 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 710 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 711 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 712 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 713 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 714 | CLANG_WARN_STRICT_PROTOTYPES = YES; 715 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 716 | CODE_SIGN_IDENTITY = "iPhone Developer"; 717 | CODE_SIGN_STYLE = Automatic; 718 | COPY_PHASE_STRIP = NO; 719 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 720 | ENABLE_NS_ASSERTIONS = NO; 721 | GCC_C_LANGUAGE_STANDARD = gnu11; 722 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 723 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 724 | INFOPLIST_FILE = PhotoBrowserDemoTests/Info.plist; 725 | IPHONEOS_DEPLOYMENT_TARGET = 11.1; 726 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 727 | MTL_ENABLE_DEBUG_INFO = NO; 728 | PRODUCT_BUNDLE_IDENTIFIER = com.appkraft.PhotoBrowserDemoTests; 729 | PRODUCT_NAME = "$(TARGET_NAME)"; 730 | TARGETED_DEVICE_FAMILY = "1,2"; 731 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PhotoBrowserDemo.app/PhotoBrowserDemo"; 732 | }; 733 | name = Release; 734 | }; 735 | /* End XCBuildConfiguration section */ 736 | 737 | /* Begin XCConfigurationList section */ 738 | 4C6F978714AF734800F8389A /* Build configuration list for PBXProject "PhotoBrowserDemo" */ = { 739 | isa = XCConfigurationList; 740 | buildConfigurations = ( 741 | 4C6F97AC14AF734900F8389A /* Debug */, 742 | 4C6F97AD14AF734900F8389A /* Release */, 743 | ); 744 | defaultConfigurationIsVisible = 0; 745 | defaultConfigurationName = Release; 746 | }; 747 | 4C6F97AE14AF734900F8389A /* Build configuration list for PBXNativeTarget "PhotoBrowserDemo" */ = { 748 | isa = XCConfigurationList; 749 | buildConfigurations = ( 750 | 4C6F97AF14AF734900F8389A /* Debug */, 751 | 4C6F97B014AF734900F8389A /* Release */, 752 | ); 753 | defaultConfigurationIsVisible = 0; 754 | defaultConfigurationName = Release; 755 | }; 756 | 7D2B79C41FD25B7600F2094F /* Build configuration list for PBXNativeTarget "PhotoBrowserDemoTests" */ = { 757 | isa = XCConfigurationList; 758 | buildConfigurations = ( 759 | 7D2B79C51FD25B7600F2094F /* Debug */, 760 | 7D2B79C61FD25B7600F2094F /* Release */, 761 | ); 762 | defaultConfigurationIsVisible = 0; 763 | defaultConfigurationName = Release; 764 | }; 765 | /* End XCConfigurationList section */ 766 | }; 767 | rootObject = 4C6F978414AF734800F8389A /* Project object */; 768 | } 769 | -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemo.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // PhotoBrowserDemo 4 | // 5 | // Created by Eduardo Callado on 11/27/16. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | var window: UIWindow? 14 | var viewController: UIViewController? 15 | } 16 | 17 | // MARK: - Application State 18 | 19 | extension AppDelegate { 20 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool { 21 | 22 | self.window = UIWindow.init(frame: UIScreen.main.bounds) 23 | 24 | let menuVC = MenuViewController.init(style: .grouped) 25 | 26 | self.viewController = UINavigationController.init(rootViewController: menuVC) 27 | 28 | self.window?.rootViewController = self.viewController 29 | self.window?.makeKeyAndVisible() 30 | 31 | return true 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemo/Custom Images/IDMPhotoBrowser_customArrowLeft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Demo/PhotoBrowserDemo/Custom Images/IDMPhotoBrowser_customArrowLeft.png -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemo/Custom Images/IDMPhotoBrowser_customArrowLeft@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Demo/PhotoBrowserDemo/Custom Images/IDMPhotoBrowser_customArrowLeft@2x.png -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemo/Custom Images/IDMPhotoBrowser_customArrowLeftSelected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Demo/PhotoBrowserDemo/Custom Images/IDMPhotoBrowser_customArrowLeftSelected.png -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemo/Custom Images/IDMPhotoBrowser_customArrowLeftSelected@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Demo/PhotoBrowserDemo/Custom Images/IDMPhotoBrowser_customArrowLeftSelected@2x.png -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemo/Custom Images/IDMPhotoBrowser_customArrowRight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Demo/PhotoBrowserDemo/Custom Images/IDMPhotoBrowser_customArrowRight.png -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemo/Custom Images/IDMPhotoBrowser_customArrowRight@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Demo/PhotoBrowserDemo/Custom Images/IDMPhotoBrowser_customArrowRight@2x.png -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemo/Custom Images/IDMPhotoBrowser_customArrowRightSelected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Demo/PhotoBrowserDemo/Custom Images/IDMPhotoBrowser_customArrowRightSelected.png -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemo/Custom Images/IDMPhotoBrowser_customArrowRightSelected@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Demo/PhotoBrowserDemo/Custom Images/IDMPhotoBrowser_customArrowRightSelected@2x.png -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemo/Custom Images/IDMPhotoBrowser_customDoneButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Demo/PhotoBrowserDemo/Custom Images/IDMPhotoBrowser_customDoneButton.png -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemo/Custom Images/IDMPhotoBrowser_customDoneButton@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Demo/PhotoBrowserDemo/Custom Images/IDMPhotoBrowser_customDoneButton@2x.png -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemo/Images.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 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemo/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "filename" : "Default@2x.png", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "extent" : "full-screen", 13 | "idiom" : "iphone", 14 | "subtype" : "retina4", 15 | "filename" : "Default-568h@2x.png", 16 | "minimum-system-version" : "7.0", 17 | "orientation" : "portrait", 18 | "scale" : "2x" 19 | }, 20 | { 21 | "orientation" : "portrait", 22 | "idiom" : "ipad", 23 | "extent" : "full-screen", 24 | "minimum-system-version" : "7.0", 25 | "scale" : "1x" 26 | }, 27 | { 28 | "orientation" : "landscape", 29 | "idiom" : "ipad", 30 | "extent" : "full-screen", 31 | "minimum-system-version" : "7.0", 32 | "scale" : "1x" 33 | }, 34 | { 35 | "orientation" : "portrait", 36 | "idiom" : "ipad", 37 | "extent" : "full-screen", 38 | "minimum-system-version" : "7.0", 39 | "scale" : "2x" 40 | }, 41 | { 42 | "orientation" : "landscape", 43 | "idiom" : "ipad", 44 | "extent" : "full-screen", 45 | "minimum-system-version" : "7.0", 46 | "scale" : "2x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemo/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Demo/PhotoBrowserDemo/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemo/Images.xcassets/LaunchImage.launchimage/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Demo/PhotoBrowserDemo/Images.xcassets/LaunchImage.launchimage/Default@2x.png -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemo/Launch Screen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 24 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemo/MenuViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuViewController.swift 3 | // PhotoBrowserDemo 4 | // 5 | // Created by Eduardo Callado on 11/27/16. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | class MenuViewController: UITableViewController, IDMPhotoBrowserDelegate { } 12 | 13 | // MARK: View Lifecycle 14 | 15 | extension MenuViewController { 16 | override func viewDidLoad() { 17 | self.setupTableViewFooterView() 18 | } 19 | } 20 | 21 | // MARK: Layout 22 | 23 | extension MenuViewController { 24 | override var prefersStatusBarHidden: Bool { 25 | return true 26 | } 27 | } 28 | 29 | // MARK: General 30 | 31 | extension MenuViewController { 32 | func setupTableViewFooterView() { 33 | let tableViewFooter: UIView = UIView(frame: CGRect.init(x: 0, y: 0, width: 320, height: 426 * 0.9 + 40)) 34 | 35 | let buttonWithImageOnScreen1 = UIButton(type: .custom) 36 | buttonWithImageOnScreen1.frame = CGRect.init(x: 15, y: 0, width: 640/3 * 0.9, height: 426/2 * 0.9) 37 | buttonWithImageOnScreen1.tag = 101 38 | buttonWithImageOnScreen1.adjustsImageWhenHighlighted = false 39 | buttonWithImageOnScreen1.setImage(UIImage.init(named: "photo1m.jpg"), for: .normal) 40 | buttonWithImageOnScreen1.imageView?.contentMode = .scaleAspectFill 41 | buttonWithImageOnScreen1.backgroundColor = UIColor.black 42 | buttonWithImageOnScreen1.addTarget(self, action: #selector(buttonWithImageOnScreenPressed(sender:)), for: .touchUpInside) 43 | tableViewFooter.addSubview(buttonWithImageOnScreen1) 44 | 45 | let buttonWithImageOnScreen2 = UIButton(type: .custom) 46 | buttonWithImageOnScreen2.frame = CGRect.init(x: 15, y: 426/2 * 0.9 + 20, width: 640/3 * 0.9, height: 426/2 * 0.9) 47 | buttonWithImageOnScreen2.tag = 102 48 | buttonWithImageOnScreen2.adjustsImageWhenHighlighted = false 49 | buttonWithImageOnScreen2.setImage(UIImage.init(named: "photo3m.jpg"), for: .normal) 50 | buttonWithImageOnScreen2.imageView?.contentMode = .scaleAspectFill 51 | buttonWithImageOnScreen2.backgroundColor = UIColor.black 52 | buttonWithImageOnScreen2.addTarget(self, action: #selector(buttonWithImageOnScreenPressed(sender:)), for: .touchUpInside) 53 | tableViewFooter.addSubview(buttonWithImageOnScreen2) 54 | 55 | self.tableView.tableFooterView = tableViewFooter; 56 | } 57 | } 58 | 59 | // MARK: Actions 60 | 61 | extension MenuViewController { 62 | func buttonWithImageOnScreenPressed(sender: AnyObject) { 63 | let buttonSender = sender as? UIButton 64 | 65 | // Create an array to store IDMPhoto objects 66 | var photos: [IDMPhoto] = [] 67 | 68 | var photo: IDMPhoto 69 | 70 | if buttonSender?.tag == 101 { 71 | let path_photo1l = [Bundle.main.path(forResource: "photo1l", ofType: "jpg")] 72 | photo = IDMPhoto.photos(withFilePaths:path_photo1l).first as! IDMPhoto 73 | photo.caption = "Grotto of the Madonna" 74 | photos.append(photo) 75 | } 76 | 77 | let path_photo3l = [Bundle.main.path(forResource: "photo3l", ofType: "jpg")] 78 | photo = IDMPhoto.photos(withFilePaths:path_photo3l).first as! IDMPhoto 79 | photo.caption = "York Floods" 80 | photos.append(photo) 81 | 82 | let path_photo2l = [Bundle.main.path(forResource: "photo2l", ofType: "jpg")] 83 | photo = IDMPhoto.photos(withFilePaths:path_photo2l).first as! IDMPhoto 84 | photo.caption = "The London Eye is a giant Ferris wheel situated on the banks of the River Thames, in London, England." 85 | photos.append(photo) 86 | 87 | let path_photo4l = [Bundle.main.path(forResource: "photo4l", ofType: "jpg")] 88 | photo = IDMPhoto.photos(withFilePaths:path_photo4l).first as! IDMPhoto 89 | photo.caption = "Campervan"; 90 | photos.append(photo) 91 | 92 | if buttonSender?.tag == 102 { 93 | let path_photo1l = [Bundle.main.path(forResource: "photo1l", ofType: "jpg")] 94 | photo = IDMPhoto.photos(withFilePaths:path_photo1l).first as! IDMPhoto 95 | photo.caption = "Grotto of the Madonna"; 96 | photos.append(photo) 97 | } 98 | 99 | // Create and setup browser 100 | let browser: IDMPhotoBrowser = IDMPhotoBrowser(photos: photos, animatedFrom: buttonSender) // using initWithPhotos:animatedFromView: 101 | browser.delegate = self 102 | browser.displayActionButton = false 103 | browser.displayArrowButton = true 104 | browser.displayCounterLabel = true 105 | browser.usePopAnimation = true 106 | browser.scaleImage = buttonSender?.currentImage 107 | browser.dismissOnTouch = true 108 | 109 | // Show 110 | self.present(browser, animated: true, completion: nil) 111 | } 112 | } 113 | 114 | // MARK: TableView Data Source 115 | 116 | extension MenuViewController { 117 | override func numberOfSections(in tableView: UITableView) -> Int { 118 | return 3 119 | } 120 | 121 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 122 | switch section { 123 | case 0: 124 | return 1 125 | case 1: 126 | return 3 127 | case 2: 128 | return 0 129 | default: 130 | return 0 131 | } 132 | } 133 | 134 | override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 135 | switch section { 136 | case 0: 137 | return "Single photo" 138 | case 1: 139 | return "Multiple photos" 140 | case 2: 141 | return "Photos on screen" 142 | default: 143 | return "" 144 | } 145 | } 146 | 147 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 148 | // Create 149 | let cellIdentifier = "Cell"; 150 | var cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) 151 | if cell == nil { 152 | cell = UITableViewCell.init(style: .default, reuseIdentifier: cellIdentifier) 153 | } 154 | 155 | // Configure 156 | if indexPath.section == 0 { 157 | cell?.textLabel?.text = "Local photo" 158 | } else if indexPath.section == 1 { 159 | switch indexPath.row { 160 | case 0: 161 | cell?.textLabel?.text = "Local photos" 162 | case 1: 163 | cell?.textLabel?.text = "Photos from Flickr" 164 | case 2: 165 | cell?.textLabel?.text = "Photos from Flickr - Custom" 166 | default: 167 | break 168 | } 169 | } 170 | 171 | return cell! 172 | } 173 | } 174 | 175 | // MARK: TableView Delegate 176 | 177 | extension MenuViewController { 178 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 179 | tableView.deselectRow(at: indexPath, animated: true) 180 | 181 | // Create an array to store IDMPhoto objects 182 | var photos: [IDMPhoto] = [] 183 | 184 | var photo: IDMPhoto 185 | 186 | if indexPath.section == 0 { // Local photo 187 | let path_photo2l = [Bundle.main.path(forResource: "photo2l", ofType: "jpg")] 188 | photo = IDMPhoto.photos(withFilePaths:path_photo2l).first as! IDMPhoto 189 | photo.caption = "The London Eye is a giant Ferris wheel situated on the banks of the River Thames, in London, England." 190 | photos.append(photo) 191 | } 192 | else if indexPath.section == 1 { // Multiple photos 193 | if indexPath.row == 0 { // Local Photos 194 | 195 | let path_photo1l = [Bundle.main.path(forResource: "photo1l", ofType: "jpg")] 196 | photo = IDMPhoto.photos(withFilePaths:path_photo1l).first as! IDMPhoto 197 | photo.caption = "Grotto of the Madonna" 198 | photos.append(photo) 199 | 200 | let path_photo2l = [Bundle.main.path(forResource: "photo2l", ofType: "jpg")] 201 | photo = IDMPhoto.photos(withFilePaths:path_photo2l).first as! IDMPhoto 202 | photo.caption = "The London Eye is a giant Ferris wheel situated on the banks of the River Thames, in London, England." 203 | photos.append(photo) 204 | 205 | let path_photo3l = [Bundle.main.path(forResource: "photo3l", ofType: "jpg")] 206 | photo = IDMPhoto.photos(withFilePaths:path_photo3l).first as! IDMPhoto 207 | photo.caption = "York Floods" 208 | photos.append(photo) 209 | 210 | let path_photo4l = [Bundle.main.path(forResource: "photo4l", ofType: "jpg")] 211 | photo = IDMPhoto.photos(withFilePaths:path_photo4l).first as! IDMPhoto 212 | photo.caption = "Campervan"; 213 | photos.append(photo) 214 | } else if indexPath.row == 1 || indexPath.row == 2 { // Photos from Flickr or Flickr - Custom 215 | let photosWithURLArray = [NSURL.init(string: "http://farm4.static.flickr.com/3567/3523321514_371d9ac42f_b.jpg"), 216 | NSURL.init(string: "http://farm4.static.flickr.com/3629/3339128908_7aecabc34b_b.jpg"), 217 | NSURL.init(string: "http://farm4.static.flickr.com/3364/3338617424_7ff836d55f_b.jpg"), 218 | NSURL.init(string: "http://farm4.static.flickr.com/3590/3329114220_5fbc5bc92b_b.jpg")] 219 | let photosWithURL: [IDMPhoto] = IDMPhoto.photos(withURLs: photosWithURLArray) as! [IDMPhoto] 220 | 221 | photos = photosWithURL 222 | } 223 | } 224 | 225 | // Create and setup browser 226 | let browser = IDMPhotoBrowser.init(photos: photos) 227 | browser?.delegate = self 228 | 229 | if indexPath.section == 1 { // Multiple photos 230 | if indexPath.row == 1 { // Photos from Flickr 231 | browser?.displayCounterLabel = true 232 | browser?.displayActionButton = false 233 | } else if indexPath.row == 2 { // Photos from Flickr - Custom 234 | browser?.actionButtonTitles = ["Option 1", "Option 2", "Option 3", "Option 4"] 235 | browser?.displayCounterLabel = true 236 | browser?.useWhiteBackgroundColor = true 237 | browser?.leftArrowImage = UIImage.init(named: "IDMPhotoBrowser_customArrowLeft.png") 238 | browser?.rightArrowImage = UIImage.init(named: "IDMPhotoBrowser_customArrowRight.png") 239 | browser?.leftArrowSelectedImage = UIImage.init(named: "IDMPhotoBrowser_customArrowLeftSelected.png") 240 | browser?.rightArrowSelectedImage = UIImage.init(named: "IDMPhotoBrowser_customArrowRightSelected.png") 241 | browser?.doneButtonImage = UIImage.init(named: "IDMPhotoBrowser_customDoneButton.png") 242 | browser?.view.tintColor = UIColor.orange 243 | browser?.progressTintColor = UIColor.orange 244 | browser?.trackTintColor = UIColor.init(white: 0.8, alpha: 1) 245 | } 246 | } 247 | 248 | // Show 249 | present(browser!, animated: true, completion: nil) 250 | 251 | tableView.deselectRow(at: indexPath, animated: true) 252 | } 253 | } 254 | 255 | // MARK: IDMPhotoBrowser Delegate 256 | 257 | extension MenuViewController { 258 | func photoBrowser(_ photoBrowser: IDMPhotoBrowser!, didShowPhotoAt index: UInt) { 259 | let photo: IDMPhoto = photoBrowser.photo(at: index) as! IDMPhoto 260 | print("Did show photoBrowser with photo index: \(index), photo caption: \(photo.caption)") 261 | } 262 | 263 | func photoBrowser(_ photoBrowser: IDMPhotoBrowser!, willDismissAtPageIndex index: UInt) { 264 | let photo: IDMPhoto = photoBrowser.photo(at: index) as! IDMPhoto 265 | print("Will dismiss photoBrowser with photo index: \(index), photo caption: \(photo.caption)") 266 | } 267 | 268 | func photoBrowser(_ photoBrowser: IDMPhotoBrowser!, didDismissAtPageIndex index: UInt) { 269 | let photo: IDMPhoto = photoBrowser.photo(at: index) as! IDMPhoto 270 | print("Did dismiss photoBrowser with photo index: \(index), photo caption: \(photo.caption)") 271 | } 272 | 273 | func photoBrowser(_ photoBrowser: IDMPhotoBrowser!, didDismissActionSheetWithButtonIndex buttonIndex: UInt, photoIndex: UInt) { 274 | let photo: IDMPhoto = photoBrowser.photo(at: buttonIndex) as! IDMPhoto 275 | print("Did dismiss photoBrowser with photo index: \(buttonIndex), photo caption: \(photo.caption)") 276 | 277 | UIAlertView(title: "Option \(buttonIndex+1)", message: nil, delegate: nil, cancelButtonTitle: "OK").show() 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemo/PhotoBrowserDemo-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "Menu.h" 6 | #import "IDMPhotoBrowser.h" 7 | -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemo/PhotoBrowserDemo-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPhotoLibraryAddUsageDescription 6 | need to access your photo Library 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleDisplayName 10 | PhotoBrowser 11 | CFBundleExecutable 12 | ${EXECUTABLE_NAME} 13 | CFBundleIcons 14 | 15 | CFBundleIcons~ipad 16 | 17 | CFBundleIdentifier 18 | $(PRODUCT_BUNDLE_IDENTIFIER) 19 | CFBundleInfoDictionaryVersion 20 | 6.0 21 | CFBundleName 22 | ${PRODUCT_NAME} 23 | CFBundlePackageType 24 | APPL 25 | CFBundleShortVersionString 26 | 1.0 27 | CFBundleSignature 28 | ???? 29 | CFBundleVersion 30 | 1.0 31 | LSRequiresIPhoneOS 32 | 33 | NSAppTransportSecurity 34 | 35 | NSAllowsArbitraryLoads 36 | 37 | 38 | UIApplicationExitsOnSuspend 39 | 40 | UILaunchStoryboardName 41 | Launch Screen 42 | UIStatusBarHidden 43 | 44 | UISupportedInterfaceOrientations 45 | 46 | UIInterfaceOrientationPortrait 47 | 48 | UISupportedInterfaceOrientations~ipad 49 | 50 | UIInterfaceOrientationPortrait 51 | 52 | UIViewControllerBasedStatusBarAppearance 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemo/PhotoBrowserDemo-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'PhotoBrowserDemo' target in the 'PhotoBrowserDemo' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_4_0 8 | #warning "This project uses features only available in iOS SDK 4.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #import 15 | #import 16 | #endif 17 | -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemo/Photos/photo1l.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Demo/PhotoBrowserDemo/Photos/photo1l.jpg -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemo/Photos/photo1m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Demo/PhotoBrowserDemo/Photos/photo1m.jpg -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemo/Photos/photo2l.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Demo/PhotoBrowserDemo/Photos/photo2l.jpg -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemo/Photos/photo2m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Demo/PhotoBrowserDemo/Photos/photo2m.jpg -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemo/Photos/photo3l.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Demo/PhotoBrowserDemo/Photos/photo3l.jpg -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemo/Photos/photo3m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Demo/PhotoBrowserDemo/Photos/photo3m.jpg -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemo/Photos/photo4l.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Demo/PhotoBrowserDemo/Photos/photo4l.jpg -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemo/Photos/photo4m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Demo/PhotoBrowserDemo/Photos/photo4m.jpg -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemoTests/IDMUtilsTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // IDMUtilsTest.m 3 | // PhotoBrowserDemoTests 4 | // 5 | // Created by Oliver ONeill on 2/12/17. 6 | // 7 | 8 | #import 9 | #import "IDMUtils.h" 10 | 11 | @interface IDMUtilsTest : XCTestCase 12 | 13 | @end 14 | 15 | @implementation IDMUtilsTest 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | } 20 | 21 | - (void)tearDown { 22 | [super tearDown]; 23 | } 24 | 25 | - (void)testAdjustRectForSafeAreaInsets { 26 | // given 27 | CGRect rect = CGRectMake(0, 0, 100, 200); 28 | CGRect bounds = rect; 29 | UIEdgeInsets insets = UIEdgeInsetsMake(10, 10, 10, 10); 30 | BOOL adjust = YES; 31 | int statusBarHeight = 0; 32 | // when 33 | CGRect result = [IDMUtils adjustRect:rect forSafeAreaInsets:insets forBounds:bounds adjustForStatusBar:adjust statusBarHeight:statusBarHeight]; 34 | // then 35 | // since its moved 10 down and 10 to the left, the width and height are then 36 | // decreased by 20 37 | CGRect expected = CGRectMake(10, 10, 80, 180); 38 | XCTAssert(CGRectEqualToRect(result, expected)); 39 | } 40 | 41 | - (void)testAdjustRectForSafeAreaInsetsDoesntModifyGivenZeroInsets { 42 | // given 43 | CGRect rect = CGRectMake(0, 0, 100, 200); 44 | CGRect bounds = rect; 45 | // no inset changes 46 | UIEdgeInsets insets = UIEdgeInsetsZero; 47 | BOOL adjust = YES; 48 | int statusBarHeight = 0; 49 | // when 50 | CGRect result = [IDMUtils adjustRect:rect forSafeAreaInsets:insets forBounds:bounds adjustForStatusBar:adjust statusBarHeight:statusBarHeight]; 51 | // then 52 | // since there were no insets, the result should not change the rect 53 | XCTAssert(CGRectEqualToRect(result, rect)); 54 | } 55 | 56 | - (void)testAdjustRectForSafeAreaInsetsWithSmallBounds { 57 | // given 58 | CGRect rect = CGRectMake(0, 0, 100, 200); 59 | // small bounds should not affect the view 60 | CGRect bounds = CGRectMake(10, 10, 10, 20); 61 | UIEdgeInsets insets = UIEdgeInsetsZero; 62 | BOOL adjust = YES; 63 | int statusBarHeight = 0; 64 | // when 65 | CGRect result = [IDMUtils adjustRect:rect forSafeAreaInsets:insets forBounds:bounds adjustForStatusBar:adjust statusBarHeight:statusBarHeight]; 66 | // then 67 | XCTAssert(CGRectEqualToRect(result, rect)); 68 | } 69 | 70 | - (void)testAdjustRectForSafeAreaInsetsWithoutStatusBarAdjustment { 71 | // given 72 | CGRect rect = CGRectMake(0, 0, 100, 200); 73 | CGRect bounds = CGRectMake(0, 0, 100, 200); 74 | UIEdgeInsets insets = UIEdgeInsetsMake(10, 10, 10, 10); 75 | BOOL adjust = NO; 76 | int statusBarHeight = 0; 77 | // when 78 | CGRect result = [IDMUtils adjustRect:rect forSafeAreaInsets:insets forBounds:bounds adjustForStatusBar:adjust statusBarHeight:statusBarHeight]; 79 | // then 80 | // since its moved 10 down and 10 to the left, the width and height are then 81 | // decreased by 20 82 | CGRect expected = CGRectMake(10, 0, 80, 190); 83 | XCTAssert(CGRectEqualToRect(result, expected)); 84 | } 85 | 86 | - (void)testAdjustRectForSafeAreaInsetsShiftsViewsUpInsteadOfResize { 87 | // given 88 | CGRect rect = CGRectMake(0, 20, 100, 200); 89 | CGRect bounds = CGRectMake(0, 0, 100, 200); 90 | UIEdgeInsets insets = UIEdgeInsetsMake(10, 10, 10, 10); 91 | BOOL adjust = NO; 92 | int statusBarHeight = 0; 93 | // when 94 | CGRect result = [IDMUtils adjustRect:rect forSafeAreaInsets:insets forBounds:bounds adjustForStatusBar:adjust statusBarHeight:statusBarHeight]; 95 | // then 96 | // the view was moved up by 10 on the y axis to move above the inset 97 | CGRect expected = CGRectMake(10, 10, 80, 200); 98 | XCTAssert(CGRectEqualToRect(result, expected)); 99 | } 100 | 101 | - (void)testAdjustRectForSafeAreaInsetsUsesStatusBarHeight { 102 | // given 103 | CGRect rect = CGRectMake(0, 0, 100, 200); 104 | CGRect bounds = CGRectMake(0, 0, 100, 200); 105 | UIEdgeInsets insets = UIEdgeInsetsZero; 106 | BOOL adjust = YES; 107 | int statusBarHeight = 10; 108 | // when 109 | CGRect result = [IDMUtils adjustRect:rect forSafeAreaInsets:insets forBounds:bounds adjustForStatusBar:adjust statusBarHeight:statusBarHeight]; 110 | // then 111 | // the view is moved down by 10 and the height is decreased 112 | CGRect expected = CGRectMake(0, 10, 100, 190); 113 | XCTAssert(CGRectEqualToRect(result, expected)); 114 | } 115 | @end 116 | -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemoTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | -------------------------------------------------------------------------------- /Demo/PhotoBrowserDemoTests/PhotoBrowserDemoTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // PhotoBrowserDemoTests.m 3 | // PhotoBrowserDemoTests 4 | // 5 | // Created by Oliver ONeill on 2/12/17. 6 | // 7 | 8 | #import 9 | 10 | @interface PhotoBrowserDemoTests : XCTestCase 11 | 12 | @end 13 | 14 | @implementation PhotoBrowserDemoTests 15 | @end 16 | -------------------------------------------------------------------------------- /Demo/Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | 3 | # ignoring warning from pods 4 | inhibit_all_warnings! 5 | 6 | target "PhotoBrowserDemo" do 7 | 8 | pod 'SDWebImage' 9 | pod 'DACircularProgress' 10 | pod 'pop' 11 | 12 | end 13 | -------------------------------------------------------------------------------- /Demo/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - DACircularProgress (2.3.1) 3 | - pop (1.0.9) 4 | - SDWebImage (4.0.0): 5 | - SDWebImage/Core (= 4.0.0) 6 | - SDWebImage/Core (4.0.0) 7 | 8 | DEPENDENCIES: 9 | - DACircularProgress 10 | - pop 11 | - SDWebImage 12 | 13 | SPEC CHECKSUMS: 14 | DACircularProgress: 4dd437c0fc3da5161cb289e07ac449493d41db71 15 | pop: f667631a5108a2e60d9e8797c9b32ddaf2080bce 16 | SDWebImage: 76a6348bdc74eb5a55dd08a091ef298e56b55e41 17 | 18 | PODFILE CHECKSUM: 7c9b8bb160246eb04b56feab0ec4a8fb149d5fa3 19 | 20 | COCOAPODS: 1.2.0 21 | -------------------------------------------------------------------------------- /IDMPhotoBrowser.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "IDMPhotoBrowser" 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.11.3" 5 | s.homepage = "https://github.com/ideaismobile/IDMPhotoBrowser" 6 | s.license = { :type => 'MIT', :file => 'LICENSE.txt' } 7 | s.author = { "Eduardo Callado" => "eduardo_tasker@hotmail.com" } 8 | s.source = { :git => "https://github.com/ideaismobile/IDMPhotoBrowser.git", :tag => "1.11.3" } 9 | s.platform = :ios, '8.0' 10 | s.source_files = 'Classes/*.{h,m}' 11 | s.resources = 'Classes/IDMPhotoBrowser.bundle', 'Classes/IDMPBLocalizations.bundle' 12 | s.framework = 'MessageUI', 'QuartzCore', 'SystemConfiguration', 'MobileCoreServices', 'Security' 13 | s.requires_arc = true 14 | s.dependency 'SDWebImage' 15 | s.dependency 'DACircularProgress' 16 | s.dependency 'pop' 17 | end 18 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Ideais Mobile 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # IDMPhotoBrowser ![](http://cocoapod-badges.herokuapp.com/v/IDMPhotoBrowser/badge.png) ![](http://cocoapod-badges.herokuapp.com/p/IDMPhotoBrowser/badge.png) 2 | 3 | IDMPhotoBrowser 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 | ## New features: 8 | - Uses ARC 9 | - Uses SDWebImage for image loading 10 | - Image progress shown 11 | - Minimalistic Facebook-like interface, swipe up/down to dismiss 12 | - Ability to add custom actions on the action sheet 13 | 14 | ## Features 15 | 16 | - Can display one or more images by providing either `UIImage` objects, file paths to images on the device, or URLs to images online 17 | - Handles the downloading and caching of photos from the web seamlessly 18 | - Photos can be zoomed and panned, and optional captions can be displayed 19 | 20 | ## Screenshots 21 | 22 | [![Alt][screenshot1_thumb]][screenshot1] [![Alt][screenshot2_thumb]][screenshot2] [![Alt][screenshot3_thumb]][screenshot3] [![Alt][screenshot4_thumb]][screenshot4] [![Alt][screenshot5_thumb]][screenshot5] 23 | 24 | [screenshot1_thumb]: https://raw.github.com/appkraft/IDMPhotoBrowser/master/Screenshots/idmphotobrowser_thumb1.png 25 | [screenshot1]: https://raw.github.com/appkraft/IDMPhotoBrowser/master/Screenshots/idmphotobrowser_ss1.png 26 | [screenshot2_thumb]: https://raw.github.com/appkraft/IDMPhotoBrowser/master/Screenshots/idmphotobrowser_thumb2.png 27 | [screenshot2]: https://raw.github.com/appkraft/IDMPhotoBrowser/master/Screenshots/idmphotobrowser_ss2.png 28 | [screenshot3_thumb]: https://raw.github.com/appkraft/IDMPhotoBrowser/master/Screenshots/idmphotobrowser_thumb3.png 29 | [screenshot3]: https://raw.github.com/appkraft/IDMPhotoBrowser/master/Screenshots/idmphotobrowser_ss3.png 30 | [screenshot4_thumb]: https://raw.github.com/appkraft/IDMPhotoBrowser/master/Screenshots/idmphotobrowser_thumb4.png 31 | [screenshot4]: https://raw.github.com/appkraft/IDMPhotoBrowser/master/Screenshots/idmphotobrowser_ss4.png 32 | [screenshot5_thumb]: https://raw.github.com/appkraft/IDMPhotoBrowser/master/Screenshots/idmphotobrowser_thumb5.png 33 | [screenshot5]: https://raw.github.com/appkraft/IDMPhotoBrowser/master/Screenshots/idmphotobrowser_ss5.png 34 | 35 | ## Usage 36 | 37 | See the code snippet below for an example of how to implement the photo browser. 38 | 39 | First create a photos array containing IDMPhoto objects: 40 | 41 | ``` objective-c 42 | // URLs array 43 | NSArray *photosURL = @[[NSURL URLWithString:@"http://farm4.static.flickr.com/3567/3523321514_371d9ac42f_b.jpg"], 44 | [NSURL URLWithString:@"http://farm4.static.flickr.com/3629/3339128908_7aecabc34b_b.jpg"], 45 | [NSURL URLWithString:@"http://farm4.static.flickr.com/3364/3338617424_7ff836d55f_b.jpg"], 46 | [NSURL URLWithString:@"http://farm4.static.flickr.com/3590/3329114220_5fbc5bc92b_b.jpg"]]; 47 | 48 | // Create an array to store IDMPhoto objects 49 | NSMutableArray *photos = [NSMutableArray new]; 50 | 51 | for (NSURL *url in photosURL) { 52 | IDMPhoto *photo = [IDMPhoto photoWithURL:url]; 53 | [photos addObject:photo]; 54 | } 55 | 56 | // Or use this constructor to receive an NSArray of IDMPhoto objects from your NSURL objects 57 | NSArray *photos = [IDMPhoto photosWithURLs:photosURL]; 58 | ```` 59 | 60 | There are two main ways to presente the photoBrowser, with a fade on screen or with a zooming effect from an existing view. 61 | 62 | Using a simple fade transition: 63 | 64 | ``` objective-c 65 | IDMPhotoBrowser *browser = [[IDMPhotoBrowser alloc] initWithPhotos:photos]; 66 | ``` 67 | 68 | Zooming effect from a view: 69 | 70 | ``` objective-c 71 | IDMPhotoBrowser *browser = [[IDMPhotoBrowser alloc] initWithPhotos:photos animatedFromView:sender]; 72 | ``` 73 | 74 | 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: 75 | 76 | ``` objective-c 77 | browser.scaleImage = buttonSender.currentImage; 78 | ``` 79 | 80 | Presenting using a modal view controller: 81 | 82 | ``` objective-c 83 | [self presentViewController:browser animated:YES completion:nil]; 84 | ``` 85 | 86 | ### Customization 87 | 88 | ##### Toolbar 89 | 90 | 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. 91 | 92 | Toolbar setup example: 93 | ``` objective-c 94 | browser.displayActionButton = NO; 95 | browser.displayArrowButton = YES; 96 | browser.displayCounterLabel = YES; 97 | ``` 98 | 99 | It is possible to use your own image on the toolbar arrows: 100 | ``` objective-c 101 | browser.leftArrowImage = [UIImage imageNamed:@"IDMPhotoBrowser_customArrowLeft.png"]; 102 | browser.rightArrowImage = [UIImage imageNamed:@"IDMPhotoBrowser_customArrowRight.png"]; 103 | browser.leftArrowSelectedImage = [UIImage imageNamed:@"IDMPhotoBrowser_customArrowLeftSelected.png"]; 104 | browser.rightArrowSelectedImage = [UIImage imageNamed:@"IDMPhotoBrowser_customArrowRightSelected.png"]; 105 | ``` 106 | 107 | 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 IDMPhotoBrowser delegate 108 | 109 | ``` objective-c 110 | browser.actionButtonTitles = @[@"Option 1", @"Option 2", @"Option 3", @"Option 4"]; 111 | ``` 112 | 113 | #### Others 114 | 115 | Others customizations you can make are: use white background color, don't display the done button and change the done button background image: 116 | ``` objective-c 117 | browser.useWhiteBackgroundColor = YES; 118 | browser.displayDoneButton = NO; 119 | browser.doneButtonImage = [UIImage imageNamed:@"IDMPhotoBrowser_customDoneButton.png"]; 120 | ``` 121 | 122 | If you want to keep the interface shown when the user is scrolling : 123 | ``` objective-c 124 | browser.autoHideInterface = NO; 125 | ``` 126 | 127 | You can use a smooth [pop](https://github.com/facebook/pop) animation when presenting and dismissing a photo: 128 | ``` objective-c 129 | browser.usePopAnimation = YES; 130 | ``` 131 | 132 | If the presenting view controller doesn't have a status bar, in some cases you can force it to be hidden: 133 | ``` objective-c 134 | browser.forceHideStatusBar = YES; 135 | ``` 136 | 137 | It's possible to disable the vertical dismiss swipe gesture: 138 | ``` objective-c 139 | browser.disableVerticalSwipe = YES; 140 | ``` 141 | 142 | Dismiss the photo browser with a touch (instead of showing/hiding controls): 143 | ``` objective-c 144 | browser.dismissOnTouch = YES; 145 | ``` 146 | 147 | ### Photo Captions 148 | 149 | Photo captions can be displayed simply by setting the `caption` property on specific photos: 150 | ``` objective-c 151 | IDMPhoto *photo = [IDMPhoto photoWithFilePath:[[NSBundle mainBundle] pathForResource:@"photo2l" ofType:@"jpg"]]; 152 | photo.caption = @"Campervan"; 153 | ``` 154 | 155 | No caption will be displayed if the caption property is not set. 156 | 157 | #### Custom Captions 158 | 159 | 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: 160 | 161 | 1. Optionally use a subclass of `IDMPhoto` for your photos so you can store more data than a simple caption string. 162 | 2. Subclass `IDMCaptionView` 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 `IDMCaptionView.h` 163 | 3. Implement the `-photoBrowser:captionViewForPhotoAtIndex:` IDMPhotoBrowser delegate method (shown below). 164 | 165 | Example delegate method for custom caption view: 166 | ``` objective-c 167 | - (IDMCaptionView *)photoBrowser:(IDMPhotoBrowser *)photoBrowser captionViewForPhotoAtIndex:(NSUInteger)index { 168 | IDMPhoto *photo = [self.photos objectAtIndex:index]; 169 | MyIDMCaptionViewSubclass *captionView = [[MyIDMCaptionViewSubclass alloc] initWithPhoto:photo]; 170 | return captionView; 171 | } 172 | ``` 173 | 174 | ## Adding to your project 175 | 176 | ### Using CocoaPods 177 | 178 | Just add `pod 'IDMPhotoBrowser'` to your Podfile. 179 | 180 | ### Including Source Directly Into Your Project 181 | 182 | #### Opensource libraries used 183 | 184 | - [SDWebImage](https://github.com/rs/SDWebImage) 185 | - [DACircularProgress](https://github.com/danielamitay/DACircularProgress) 186 | - [pop](https://github.com/facebook/pop) 187 | 188 | ## Licence 189 | 190 | This project uses MIT License. 191 | -------------------------------------------------------------------------------- /Screenshots/idmphotobrowser_ss1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Screenshots/idmphotobrowser_ss1.png -------------------------------------------------------------------------------- /Screenshots/idmphotobrowser_ss2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Screenshots/idmphotobrowser_ss2.png -------------------------------------------------------------------------------- /Screenshots/idmphotobrowser_ss3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Screenshots/idmphotobrowser_ss3.png -------------------------------------------------------------------------------- /Screenshots/idmphotobrowser_ss4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Screenshots/idmphotobrowser_ss4.png -------------------------------------------------------------------------------- /Screenshots/idmphotobrowser_ss5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Screenshots/idmphotobrowser_ss5.png -------------------------------------------------------------------------------- /Screenshots/idmphotobrowser_thumb1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Screenshots/idmphotobrowser_thumb1.png -------------------------------------------------------------------------------- /Screenshots/idmphotobrowser_thumb2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Screenshots/idmphotobrowser_thumb2.png -------------------------------------------------------------------------------- /Screenshots/idmphotobrowser_thumb3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Screenshots/idmphotobrowser_thumb3.png -------------------------------------------------------------------------------- /Screenshots/idmphotobrowser_thumb4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Screenshots/idmphotobrowser_thumb4.png -------------------------------------------------------------------------------- /Screenshots/idmphotobrowser_thumb5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiagoperes/IDMPhotoBrowser/01fbba9cfa07199be79d5023e0178fd5f07a0a14/Screenshots/idmphotobrowser_thumb5.png --------------------------------------------------------------------------------