├── FSPhotoViewDemo ├── FSPhotoViewDemo │ ├── en.lproj │ │ └── InfoPlist.strings │ ├── Default.png │ ├── Default@2x.png │ ├── demo_pic1.png │ ├── demo_pic2.png │ ├── demo_pic3.png │ ├── Default-568h@2x.png │ ├── FSViewController.h │ ├── FSPhotoViewDemo-Prefix.pch │ ├── main.m │ ├── FSPhotoView │ │ ├── FSPhotoView.h │ │ └── FSPhotoView.m │ ├── AppDelegate.h │ ├── FSPhotoViewDemo-Info.plist │ ├── AppDelegate.m │ └── FSViewController.m └── FSPhotoViewDemo.xcodeproj │ ├── xcuserdata │ └── jinxiaojun.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ ├── xcschememanagement.plist │ │ └── FSPhotoViewDemo.xcscheme │ ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── jinxiaojun.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ └── project.pbxproj ├── FSPhotoView ├── FSPhotoView.h └── FSPhotoView.m └── README.md /FSPhotoViewDemo/FSPhotoViewDemo/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /FSPhotoViewDemo/FSPhotoViewDemo/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjinxx/FSPhotoView/HEAD/FSPhotoViewDemo/FSPhotoViewDemo/Default.png -------------------------------------------------------------------------------- /FSPhotoViewDemo/FSPhotoViewDemo/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjinxx/FSPhotoView/HEAD/FSPhotoViewDemo/FSPhotoViewDemo/Default@2x.png -------------------------------------------------------------------------------- /FSPhotoViewDemo/FSPhotoViewDemo/demo_pic1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjinxx/FSPhotoView/HEAD/FSPhotoViewDemo/FSPhotoViewDemo/demo_pic1.png -------------------------------------------------------------------------------- /FSPhotoViewDemo/FSPhotoViewDemo/demo_pic2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjinxx/FSPhotoView/HEAD/FSPhotoViewDemo/FSPhotoViewDemo/demo_pic2.png -------------------------------------------------------------------------------- /FSPhotoViewDemo/FSPhotoViewDemo/demo_pic3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjinxx/FSPhotoView/HEAD/FSPhotoViewDemo/FSPhotoViewDemo/demo_pic3.png -------------------------------------------------------------------------------- /FSPhotoViewDemo/FSPhotoViewDemo/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjinxx/FSPhotoView/HEAD/FSPhotoViewDemo/FSPhotoViewDemo/Default-568h@2x.png -------------------------------------------------------------------------------- /FSPhotoViewDemo/FSPhotoViewDemo.xcodeproj/xcuserdata/jinxiaojun.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /FSPhotoViewDemo/FSPhotoViewDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /FSPhotoViewDemo/FSPhotoViewDemo.xcodeproj/project.xcworkspace/xcuserdata/jinxiaojun.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjinxx/FSPhotoView/HEAD/FSPhotoViewDemo/FSPhotoViewDemo.xcodeproj/project.xcworkspace/xcuserdata/jinxiaojun.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /FSPhotoViewDemo/FSPhotoViewDemo/FSViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // FSViewController.h 3 | // FSPhotoViewDemo 4 | // 5 | // Created by Stephen Jin on 8/30/13. 6 | // Copyright (c) 2013 Stephen Jin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface FSViewController : UITableViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /FSPhotoViewDemo/FSPhotoViewDemo/FSPhotoViewDemo-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'FSPhotoViewDemo' target in the 'FSPhotoViewDemo' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_3_0 8 | #warning "This project uses features only available in iOS SDK 3.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #endif 15 | -------------------------------------------------------------------------------- /FSPhotoViewDemo/FSPhotoViewDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // FSPhotoViewDemo 4 | // 5 | // Created by Stephen Jin on 8/30/13. 6 | // Copyright (c) 2013 Stephen Jin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "AppDelegate.h" 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /FSPhotoView/FSPhotoView.h: -------------------------------------------------------------------------------- 1 | // 2 | // FSPhotoView.h 3 | // FSPhotoView 4 | // 5 | // Created by Stephen Jin on 7/26/13. 6 | // Copyright (c) 2013 Stephen Jin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef void (^DisappearBlcok)(); 12 | 13 | @interface FSPhotoView : UIScrollView 14 | 15 | + (FSPhotoView *)showImageWithSenderView:(UIImageView*)senderView; 16 | + (FSPhotoView *)showImageWithSenderView:(UIImageView*)senderView completion:(DisappearBlcok)completed; 17 | 18 | @end -------------------------------------------------------------------------------- /FSPhotoViewDemo/FSPhotoViewDemo/FSPhotoView/FSPhotoView.h: -------------------------------------------------------------------------------- 1 | // 2 | // FSPhotoView.h 3 | // FSPhotoView 4 | // 5 | // Created by Stephen Jin on 7/26/13. 6 | // Copyright (c) 2013 Stephen Jin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef void (^DisappearBlcok)(); 12 | 13 | @interface FSPhotoView : UIScrollView 14 | 15 | + (FSPhotoView *)showImageWithSenderView:(UIImageView*)senderView; 16 | + (FSPhotoView *)showImageWithSenderView:(UIImageView*)senderView completion:(DisappearBlcok)completed; 17 | 18 | @end -------------------------------------------------------------------------------- /FSPhotoViewDemo/FSPhotoViewDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // FSPhotoViewDemo 4 | // 5 | // Created by Stephen Jin on 8/30/13. 6 | // Copyright (c) 2013 Stephen Jin. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FSViewController.h" 11 | 12 | @interface AppDelegate : UIResponder 13 | 14 | @property (strong, nonatomic) UIWindow *window; 15 | @property (nonatomic, retain) UINavigationController *navigationController; 16 | @property (nonatomic, retain) FSViewController *viewController; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /FSPhotoViewDemo/FSPhotoViewDemo.xcodeproj/xcuserdata/jinxiaojun.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | FSPhotoViewDemo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 49AC2F3917D083CD00606BAE 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FSPhotoView 2 | =========== 3 | 4 | FSPhotoView gives the ability to display image in fullscreen by a simple tap on any thumbnail image. The thumbnail image will animated to focused fullscreen image view from its initial position. Another tap on the fullscreen view zooms out the image back to the thumbnail status. 5 | 6 | This project is an attempt to re-implement the experience of inaction from Sina Weibo/WangYi News timeline to photo browser and to figure out how the animations are done. 7 | 8 | 9 | 10 | 11 | Please feel free to improve it, send comments or suggestions. Please let me know if you have great idea on it. 12 | You can contact me through Email: xiaojun.jin@outlook.com or Skype: xiaojun.jin 13 | 14 | -------------------------------------------------------------------------------- /FSPhotoViewDemo/FSPhotoViewDemo/FSPhotoViewDemo-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | ArcSoft.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /FSPhotoViewDemo/FSPhotoViewDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // FSPhotoViewDemo 4 | // 5 | // Created by Stephen Jin on 8/30/13. 6 | // Copyright (c) 2013 Stephen Jin. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @implementation AppDelegate 12 | 13 | - (void)dealloc 14 | { 15 | [_window release]; 16 | self.viewController = nil; 17 | self.navigationController = nil; 18 | [super dealloc]; 19 | } 20 | 21 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 22 | { 23 | self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease]; 24 | // Override point for customization after application launch. 25 | self.window.backgroundColor = [UIColor whiteColor]; 26 | 27 | self.viewController = [[[FSViewController alloc] initWithStyle:UITableViewStylePlain] autorelease]; 28 | self.navigationController = [[[UINavigationController alloc]initWithRootViewController:self.viewController] autorelease]; 29 | [self.window addSubview:self.navigationController.view]; 30 | 31 | [self.window makeKeyAndVisible]; 32 | return YES; 33 | } 34 | 35 | - (void)applicationWillResignActive:(UIApplication *)application 36 | { 37 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 38 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 39 | } 40 | 41 | - (void)applicationDidEnterBackground:(UIApplication *)application 42 | { 43 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 44 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 45 | } 46 | 47 | - (void)applicationWillEnterForeground:(UIApplication *)application 48 | { 49 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 50 | } 51 | 52 | - (void)applicationDidBecomeActive:(UIApplication *)application 53 | { 54 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 55 | } 56 | 57 | - (void)applicationWillTerminate:(UIApplication *)application 58 | { 59 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /FSPhotoViewDemo/FSPhotoViewDemo.xcodeproj/xcuserdata/jinxiaojun.xcuserdatad/xcschemes/FSPhotoViewDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 51 | 52 | 58 | 59 | 60 | 61 | 62 | 63 | 69 | 70 | 76 | 77 | 78 | 79 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /FSPhotoViewDemo/FSPhotoViewDemo/FSViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // FSViewController.m 3 | // FSPhotoViewDemo 4 | // 5 | // Created by Stephen Jin on 8/30/13. 6 | // Copyright (c) 2013 Stephen Jin. All rights reserved. 7 | // 8 | 9 | #import "FSViewController.h" 10 | #import "FSPhotoView.h" 11 | 12 | @interface FSViewController () 13 | 14 | @end 15 | 16 | @implementation FSViewController 17 | 18 | - (id)initWithStyle:(UITableViewStyle)style 19 | { 20 | self = [super initWithStyle:style]; 21 | if (self) { 22 | // Custom initialization 23 | [self setTitle:@"FSPhotoView Demo"]; 24 | [self.tableView setBackgroundColor:[UIColor clearColor]]; 25 | [self.tableView setSeparatorStyle:UITableViewCellSeparatorStyleNone]; 26 | 27 | } 28 | return self; 29 | } 30 | 31 | - (void)viewDidLoad 32 | { 33 | [super viewDidLoad]; 34 | 35 | // Uncomment the following line to preserve selection between presentations. 36 | // self.clearsSelectionOnViewWillAppear = NO; 37 | 38 | // Uncomment the following line to display an Edit button in the navigation bar for this view controller. 39 | // self.navigationItem.rightBarButtonItem = self.editButtonItem; 40 | } 41 | 42 | - (void)didReceiveMemoryWarning 43 | { 44 | [super didReceiveMemoryWarning]; 45 | // Dispose of any resources that can be recreated. 46 | } 47 | 48 | #pragma mark - Table view data source 49 | 50 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 51 | { 52 | // Return the number of sections. 53 | return 1; 54 | } 55 | 56 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 57 | { 58 | // Return the number of rows in the section. 59 | return 2; 60 | } 61 | 62 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 63 | { 64 | return indexPath.row?205:305; 65 | } 66 | 67 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 68 | { 69 | static NSString *CellIdentifier = @"Cell"; 70 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 71 | 72 | // Configure the cell... 73 | if (cell == nil) 74 | { 75 | cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease]; 76 | [cell setAccessoryType:UITableViewCellAccessoryNone]; 77 | [cell setSelectionStyle:UITableViewCellSelectionStyleNone]; 78 | } 79 | else 80 | { 81 | while ([cell.contentView.subviews lastObject] != nil) 82 | { 83 | [(UIView*)[cell.contentView.subviews lastObject] removeFromSuperview]; 84 | } 85 | } 86 | 87 | if (indexPath.row == 0) 88 | { 89 | for (int i = 0; i < 2; i++) 90 | { 91 | UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(5+i*(155+2.5), 0, 152.5, 300)]; 92 | [imageView setContentMode:UIViewContentModeScaleAspectFill]; 93 | [imageView setClipsToBounds:YES]; 94 | [imageView setUserInteractionEnabled:YES]; 95 | [imageView setImage:[UIImage imageNamed:[NSString stringWithFormat:@"demo_pic%d",i+1]]]; 96 | [cell.contentView addSubview:imageView]; 97 | 98 | UITapGestureRecognizer *tapGes = [[UITapGestureRecognizer alloc] initWithTarget:self 99 | action:@selector(tapClickAction:)]; 100 | [imageView addGestureRecognizer:tapGes]; 101 | [tapGes release]; 102 | 103 | [imageView release]; 104 | } 105 | } 106 | else 107 | { 108 | UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(5, 0, 310, 200)]; 109 | [imageView setContentMode:UIViewContentModeScaleAspectFill]; 110 | [imageView setClipsToBounds:YES]; 111 | [imageView setUserInteractionEnabled:YES]; 112 | [imageView setImage:[UIImage imageNamed:@"demo_pic3"]]; 113 | [cell.contentView addSubview:imageView]; 114 | 115 | UITapGestureRecognizer *tapGes = [[UITapGestureRecognizer alloc] initWithTarget:self 116 | action:@selector(tapClickAction:)]; 117 | [imageView addGestureRecognizer:tapGes]; 118 | [tapGes release]; 119 | 120 | [imageView release]; 121 | } 122 | return cell; 123 | } 124 | 125 | - (void)tapClickAction:(UITapGestureRecognizer *)sender 126 | { 127 | [FSPhotoView showImageWithSenderView:(UIImageView*)sender.view]; 128 | } 129 | 130 | @end 131 | -------------------------------------------------------------------------------- /FSPhotoViewDemo/FSPhotoViewDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 496B9C9217D0848500712F95 /* FSViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 496B9C9117D0848500712F95 /* FSViewController.m */; }; 11 | 496B9C9417D0896E00712F95 /* demo_pic1.png in Resources */ = {isa = PBXBuildFile; fileRef = 496B9C9317D0896E00712F95 /* demo_pic1.png */; }; 12 | 496B9C9817D08E6200712F95 /* demo_pic2.png in Resources */ = {isa = PBXBuildFile; fileRef = 496B9C9717D08E6200712F95 /* demo_pic2.png */; }; 13 | 496B9C9A17D08F6000712F95 /* demo_pic3.png in Resources */ = {isa = PBXBuildFile; fileRef = 496B9C9917D08F6000712F95 /* demo_pic3.png */; }; 14 | 496B9C9E17D0936100712F95 /* FSPhotoView.m in Sources */ = {isa = PBXBuildFile; fileRef = 496B9C9D17D092D900712F95 /* FSPhotoView.m */; }; 15 | 49AC2F3E17D083CD00606BAE /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 49AC2F3D17D083CD00606BAE /* UIKit.framework */; }; 16 | 49AC2F4017D083CD00606BAE /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 49AC2F3F17D083CD00606BAE /* Foundation.framework */; }; 17 | 49AC2F4217D083CD00606BAE /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 49AC2F4117D083CD00606BAE /* CoreGraphics.framework */; }; 18 | 49AC2F4817D083CD00606BAE /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 49AC2F4617D083CD00606BAE /* InfoPlist.strings */; }; 19 | 49AC2F4A17D083CD00606BAE /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 49AC2F4917D083CD00606BAE /* main.m */; }; 20 | 49AC2F4E17D083CD00606BAE /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 49AC2F4D17D083CD00606BAE /* AppDelegate.m */; }; 21 | 49AC2F5017D083CD00606BAE /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = 49AC2F4F17D083CD00606BAE /* Default.png */; }; 22 | 49AC2F5217D083CD00606BAE /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 49AC2F5117D083CD00606BAE /* Default@2x.png */; }; 23 | 49AC2F5417D083CD00606BAE /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 49AC2F5317D083CD00606BAE /* Default-568h@2x.png */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXFileReference section */ 27 | 496B9C9017D0848500712F95 /* FSViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSViewController.h; sourceTree = ""; }; 28 | 496B9C9117D0848500712F95 /* FSViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FSViewController.m; sourceTree = ""; }; 29 | 496B9C9317D0896E00712F95 /* demo_pic1.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = demo_pic1.png; sourceTree = ""; }; 30 | 496B9C9717D08E6200712F95 /* demo_pic2.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = demo_pic2.png; sourceTree = ""; }; 31 | 496B9C9917D08F6000712F95 /* demo_pic3.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = demo_pic3.png; sourceTree = ""; }; 32 | 496B9C9C17D092D900712F95 /* FSPhotoView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = FSPhotoView.h; path = FSPhotoView/FSPhotoView.h; sourceTree = ""; }; 33 | 496B9C9D17D092D900712F95 /* FSPhotoView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = FSPhotoView.m; path = FSPhotoView/FSPhotoView.m; sourceTree = ""; }; 34 | 49AC2F3A17D083CD00606BAE /* FSPhotoViewDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FSPhotoViewDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | 49AC2F3D17D083CD00606BAE /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 36 | 49AC2F3F17D083CD00606BAE /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 37 | 49AC2F4117D083CD00606BAE /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 38 | 49AC2F4517D083CD00606BAE /* FSPhotoViewDemo-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "FSPhotoViewDemo-Info.plist"; sourceTree = ""; }; 39 | 49AC2F4717D083CD00606BAE /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 40 | 49AC2F4917D083CD00606BAE /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 41 | 49AC2F4B17D083CD00606BAE /* FSPhotoViewDemo-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FSPhotoViewDemo-Prefix.pch"; sourceTree = ""; }; 42 | 49AC2F4C17D083CD00606BAE /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 43 | 49AC2F4D17D083CD00606BAE /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 44 | 49AC2F4F17D083CD00606BAE /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Default.png; sourceTree = ""; }; 45 | 49AC2F5117D083CD00606BAE /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default@2x.png"; sourceTree = ""; }; 46 | 49AC2F5317D083CD00606BAE /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; 47 | /* End PBXFileReference section */ 48 | 49 | /* Begin PBXFrameworksBuildPhase section */ 50 | 49AC2F3717D083CD00606BAE /* Frameworks */ = { 51 | isa = PBXFrameworksBuildPhase; 52 | buildActionMask = 2147483647; 53 | files = ( 54 | 49AC2F3E17D083CD00606BAE /* UIKit.framework in Frameworks */, 55 | 49AC2F4017D083CD00606BAE /* Foundation.framework in Frameworks */, 56 | 49AC2F4217D083CD00606BAE /* CoreGraphics.framework in Frameworks */, 57 | ); 58 | runOnlyForDeploymentPostprocessing = 0; 59 | }; 60 | /* End PBXFrameworksBuildPhase section */ 61 | 62 | /* Begin PBXGroup section */ 63 | 496B9C9B17D092CA00712F95 /* FSPhotoView */ = { 64 | isa = PBXGroup; 65 | children = ( 66 | 496B9C9C17D092D900712F95 /* FSPhotoView.h */, 67 | 496B9C9D17D092D900712F95 /* FSPhotoView.m */, 68 | ); 69 | name = FSPhotoView; 70 | sourceTree = ""; 71 | }; 72 | 49AC2F3117D083CD00606BAE = { 73 | isa = PBXGroup; 74 | children = ( 75 | 49AC2F4317D083CD00606BAE /* FSPhotoViewDemo */, 76 | 49AC2F3C17D083CD00606BAE /* Frameworks */, 77 | 49AC2F3B17D083CD00606BAE /* Products */, 78 | ); 79 | sourceTree = ""; 80 | }; 81 | 49AC2F3B17D083CD00606BAE /* Products */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | 49AC2F3A17D083CD00606BAE /* FSPhotoViewDemo.app */, 85 | ); 86 | name = Products; 87 | sourceTree = ""; 88 | }; 89 | 49AC2F3C17D083CD00606BAE /* Frameworks */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | 49AC2F3D17D083CD00606BAE /* UIKit.framework */, 93 | 49AC2F3F17D083CD00606BAE /* Foundation.framework */, 94 | 49AC2F4117D083CD00606BAE /* CoreGraphics.framework */, 95 | ); 96 | name = Frameworks; 97 | sourceTree = ""; 98 | }; 99 | 49AC2F4317D083CD00606BAE /* FSPhotoViewDemo */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | 496B9C9B17D092CA00712F95 /* FSPhotoView */, 103 | 49AC2F4C17D083CD00606BAE /* AppDelegate.h */, 104 | 49AC2F4D17D083CD00606BAE /* AppDelegate.m */, 105 | 496B9C9017D0848500712F95 /* FSViewController.h */, 106 | 496B9C9117D0848500712F95 /* FSViewController.m */, 107 | 49AC2F4417D083CD00606BAE /* Supporting Files */, 108 | ); 109 | path = FSPhotoViewDemo; 110 | sourceTree = ""; 111 | }; 112 | 49AC2F4417D083CD00606BAE /* Supporting Files */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | 496B9C9317D0896E00712F95 /* demo_pic1.png */, 116 | 496B9C9717D08E6200712F95 /* demo_pic2.png */, 117 | 496B9C9917D08F6000712F95 /* demo_pic3.png */, 118 | 49AC2F4517D083CD00606BAE /* FSPhotoViewDemo-Info.plist */, 119 | 49AC2F4617D083CD00606BAE /* InfoPlist.strings */, 120 | 49AC2F4917D083CD00606BAE /* main.m */, 121 | 49AC2F4B17D083CD00606BAE /* FSPhotoViewDemo-Prefix.pch */, 122 | 49AC2F4F17D083CD00606BAE /* Default.png */, 123 | 49AC2F5117D083CD00606BAE /* Default@2x.png */, 124 | 49AC2F5317D083CD00606BAE /* Default-568h@2x.png */, 125 | ); 126 | name = "Supporting Files"; 127 | sourceTree = ""; 128 | }; 129 | /* End PBXGroup section */ 130 | 131 | /* Begin PBXNativeTarget section */ 132 | 49AC2F3917D083CD00606BAE /* FSPhotoViewDemo */ = { 133 | isa = PBXNativeTarget; 134 | buildConfigurationList = 49AC2F5717D083CD00606BAE /* Build configuration list for PBXNativeTarget "FSPhotoViewDemo" */; 135 | buildPhases = ( 136 | 49AC2F3617D083CD00606BAE /* Sources */, 137 | 49AC2F3717D083CD00606BAE /* Frameworks */, 138 | 49AC2F3817D083CD00606BAE /* Resources */, 139 | ); 140 | buildRules = ( 141 | ); 142 | dependencies = ( 143 | ); 144 | name = FSPhotoViewDemo; 145 | productName = FSPhotoViewDemo; 146 | productReference = 49AC2F3A17D083CD00606BAE /* FSPhotoViewDemo.app */; 147 | productType = "com.apple.product-type.application"; 148 | }; 149 | /* End PBXNativeTarget section */ 150 | 151 | /* Begin PBXProject section */ 152 | 49AC2F3217D083CD00606BAE /* Project object */ = { 153 | isa = PBXProject; 154 | attributes = { 155 | LastUpgradeCheck = 0460; 156 | ORGANIZATIONNAME = "Stephen Jin"; 157 | }; 158 | buildConfigurationList = 49AC2F3517D083CD00606BAE /* Build configuration list for PBXProject "FSPhotoViewDemo" */; 159 | compatibilityVersion = "Xcode 3.2"; 160 | developmentRegion = English; 161 | hasScannedForEncodings = 0; 162 | knownRegions = ( 163 | en, 164 | ); 165 | mainGroup = 49AC2F3117D083CD00606BAE; 166 | productRefGroup = 49AC2F3B17D083CD00606BAE /* Products */; 167 | projectDirPath = ""; 168 | projectRoot = ""; 169 | targets = ( 170 | 49AC2F3917D083CD00606BAE /* FSPhotoViewDemo */, 171 | ); 172 | }; 173 | /* End PBXProject section */ 174 | 175 | /* Begin PBXResourcesBuildPhase section */ 176 | 49AC2F3817D083CD00606BAE /* Resources */ = { 177 | isa = PBXResourcesBuildPhase; 178 | buildActionMask = 2147483647; 179 | files = ( 180 | 49AC2F4817D083CD00606BAE /* InfoPlist.strings in Resources */, 181 | 49AC2F5017D083CD00606BAE /* Default.png in Resources */, 182 | 496B9C9A17D08F6000712F95 /* demo_pic3.png in Resources */, 183 | 496B9C9417D0896E00712F95 /* demo_pic1.png in Resources */, 184 | 49AC2F5217D083CD00606BAE /* Default@2x.png in Resources */, 185 | 496B9C9817D08E6200712F95 /* demo_pic2.png in Resources */, 186 | 49AC2F5417D083CD00606BAE /* Default-568h@2x.png in Resources */, 187 | ); 188 | runOnlyForDeploymentPostprocessing = 0; 189 | }; 190 | /* End PBXResourcesBuildPhase section */ 191 | 192 | /* Begin PBXSourcesBuildPhase section */ 193 | 49AC2F3617D083CD00606BAE /* Sources */ = { 194 | isa = PBXSourcesBuildPhase; 195 | buildActionMask = 2147483647; 196 | files = ( 197 | 496B9C9E17D0936100712F95 /* FSPhotoView.m in Sources */, 198 | 49AC2F4A17D083CD00606BAE /* main.m in Sources */, 199 | 49AC2F4E17D083CD00606BAE /* AppDelegate.m in Sources */, 200 | 496B9C9217D0848500712F95 /* FSViewController.m in Sources */, 201 | ); 202 | runOnlyForDeploymentPostprocessing = 0; 203 | }; 204 | /* End PBXSourcesBuildPhase section */ 205 | 206 | /* Begin PBXVariantGroup section */ 207 | 49AC2F4617D083CD00606BAE /* InfoPlist.strings */ = { 208 | isa = PBXVariantGroup; 209 | children = ( 210 | 49AC2F4717D083CD00606BAE /* en */, 211 | ); 212 | name = InfoPlist.strings; 213 | sourceTree = ""; 214 | }; 215 | /* End PBXVariantGroup section */ 216 | 217 | /* Begin XCBuildConfiguration section */ 218 | 49AC2F5517D083CD00606BAE /* Debug */ = { 219 | isa = XCBuildConfiguration; 220 | buildSettings = { 221 | ALWAYS_SEARCH_USER_PATHS = NO; 222 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 223 | CLANG_CXX_LIBRARY = "libc++"; 224 | CLANG_WARN_CONSTANT_CONVERSION = YES; 225 | CLANG_WARN_EMPTY_BODY = YES; 226 | CLANG_WARN_ENUM_CONVERSION = YES; 227 | CLANG_WARN_INT_CONVERSION = YES; 228 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 229 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 230 | COPY_PHASE_STRIP = NO; 231 | GCC_C_LANGUAGE_STANDARD = gnu99; 232 | GCC_DYNAMIC_NO_PIC = NO; 233 | GCC_OPTIMIZATION_LEVEL = 0; 234 | GCC_PREPROCESSOR_DEFINITIONS = ( 235 | "DEBUG=1", 236 | "$(inherited)", 237 | ); 238 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 239 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 240 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 241 | GCC_WARN_UNUSED_VARIABLE = YES; 242 | IPHONEOS_DEPLOYMENT_TARGET = 6.1; 243 | ONLY_ACTIVE_ARCH = YES; 244 | SDKROOT = iphoneos; 245 | }; 246 | name = Debug; 247 | }; 248 | 49AC2F5617D083CD00606BAE /* Release */ = { 249 | isa = XCBuildConfiguration; 250 | buildSettings = { 251 | ALWAYS_SEARCH_USER_PATHS = NO; 252 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 253 | CLANG_CXX_LIBRARY = "libc++"; 254 | CLANG_WARN_CONSTANT_CONVERSION = YES; 255 | CLANG_WARN_EMPTY_BODY = YES; 256 | CLANG_WARN_ENUM_CONVERSION = YES; 257 | CLANG_WARN_INT_CONVERSION = YES; 258 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 259 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 260 | COPY_PHASE_STRIP = YES; 261 | GCC_C_LANGUAGE_STANDARD = gnu99; 262 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 263 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 264 | GCC_WARN_UNUSED_VARIABLE = YES; 265 | IPHONEOS_DEPLOYMENT_TARGET = 6.1; 266 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 267 | SDKROOT = iphoneos; 268 | VALIDATE_PRODUCT = YES; 269 | }; 270 | name = Release; 271 | }; 272 | 49AC2F5817D083CD00606BAE /* Debug */ = { 273 | isa = XCBuildConfiguration; 274 | buildSettings = { 275 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 276 | GCC_PREFIX_HEADER = "FSPhotoViewDemo/FSPhotoViewDemo-Prefix.pch"; 277 | INFOPLIST_FILE = "FSPhotoViewDemo/FSPhotoViewDemo-Info.plist"; 278 | PRODUCT_NAME = "$(TARGET_NAME)"; 279 | WRAPPER_EXTENSION = app; 280 | }; 281 | name = Debug; 282 | }; 283 | 49AC2F5917D083CD00606BAE /* Release */ = { 284 | isa = XCBuildConfiguration; 285 | buildSettings = { 286 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 287 | GCC_PREFIX_HEADER = "FSPhotoViewDemo/FSPhotoViewDemo-Prefix.pch"; 288 | INFOPLIST_FILE = "FSPhotoViewDemo/FSPhotoViewDemo-Info.plist"; 289 | PRODUCT_NAME = "$(TARGET_NAME)"; 290 | WRAPPER_EXTENSION = app; 291 | }; 292 | name = Release; 293 | }; 294 | /* End XCBuildConfiguration section */ 295 | 296 | /* Begin XCConfigurationList section */ 297 | 49AC2F3517D083CD00606BAE /* Build configuration list for PBXProject "FSPhotoViewDemo" */ = { 298 | isa = XCConfigurationList; 299 | buildConfigurations = ( 300 | 49AC2F5517D083CD00606BAE /* Debug */, 301 | 49AC2F5617D083CD00606BAE /* Release */, 302 | ); 303 | defaultConfigurationIsVisible = 0; 304 | defaultConfigurationName = Release; 305 | }; 306 | 49AC2F5717D083CD00606BAE /* Build configuration list for PBXNativeTarget "FSPhotoViewDemo" */ = { 307 | isa = XCConfigurationList; 308 | buildConfigurations = ( 309 | 49AC2F5817D083CD00606BAE /* Debug */, 310 | 49AC2F5917D083CD00606BAE /* Release */, 311 | ); 312 | defaultConfigurationIsVisible = 0; 313 | defaultConfigurationName = Release; 314 | }; 315 | /* End XCConfigurationList section */ 316 | }; 317 | rootObject = 49AC2F3217D083CD00606BAE /* Project object */; 318 | } 319 | -------------------------------------------------------------------------------- /FSPhotoView/FSPhotoView.m: -------------------------------------------------------------------------------- 1 | // 2 | // FSPhotoView.m 3 | // FSPhotoView 4 | // 5 | // Created by Stephen Jin on 7/26/13. 6 | // Copyright (c) 2013 Stephen Jin. All rights reserved. 7 | // 8 | 9 | #import "FSPhotoView.h" 10 | 11 | @interface FSPhotoView () { 12 | BOOL _disableAutoLayout; 13 | UIViewContentMode ivContentMode; 14 | } 15 | @property (nonatomic, retain) UIImageView *imageView; 16 | @property (nonatomic, assign) CGRect senderFrame; 17 | @property (nonatomic, assign) CGRect clipsFrame; 18 | @property (nonatomic, assign) CGFloat offscreenY; 19 | @property (copy) DisappearBlcok block; 20 | 21 | - (void)displayImage:(UIImage *)newImage withSenderView:(UIImageView*)senderView; 22 | @end 23 | 24 | @implementation FSPhotoView 25 | 26 | + (FSPhotoView *)showImageWithSenderView:(UIImageView*)senderView; 27 | { 28 | if (nil == senderView.image) return nil; 29 | return [FSPhotoView showImageWithSenderView:senderView completion:nil]; 30 | } 31 | 32 | + (FSPhotoView *)showImageWithSenderView:(UIImageView*)senderView completion:(DisappearBlcok)completed; 33 | { 34 | if (nil == senderView.image) return nil; 35 | 36 | // Deal with staus bar. 37 | CGRect rect = [UIScreen mainScreen].bounds; 38 | 39 | if (![UIApplication sharedApplication].statusBarHidden) 40 | { 41 | rect.size.height = [UIScreen mainScreen].bounds.size.height-20.f; 42 | rect.origin.y += [UIApplication sharedApplication].statusBarHidden?0:20.f; 43 | } 44 | 45 | FSPhotoView *photoView = [[FSPhotoView alloc] initWithFrame:rect andCompleteBlock:completed]; 46 | if (photoView) 47 | { 48 | [[UIApplication sharedApplication].keyWindow addSubview:photoView]; 49 | [photoView displayImage:senderView.image withSenderView:senderView]; 50 | } 51 | return [photoView autorelease]; 52 | } 53 | 54 | - (id)initWithFrame:(CGRect)frame andCompleteBlock:(DisappearBlcok)block 55 | { 56 | self = [super initWithFrame:frame]; 57 | if (self) { 58 | // Initialization code 59 | self.block = block; 60 | [self setDelegate:self]; 61 | [self setMaximumZoomScale:2.f]; 62 | [self setShowsHorizontalScrollIndicator:NO]; 63 | [self setShowsVerticalScrollIndicator:NO]; 64 | [self loadSubviewsWithFrame:frame]; 65 | UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(removePhotoView)]; 66 | [singleTap setNumberOfTapsRequired:1]; 67 | [self addGestureRecognizer:singleTap]; 68 | [singleTap release]; 69 | UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(zoomToLocation:)]; 70 | [doubleTap setNumberOfTapsRequired:2]; 71 | [self addGestureRecognizer:doubleTap]; 72 | [singleTap requireGestureRecognizerToFail:doubleTap]; 73 | [doubleTap release]; 74 | } 75 | return self; 76 | } 77 | 78 | - (void)loadSubviewsWithFrame:(CGRect)frame 79 | { 80 | self.imageView = [[[UIImageView alloc] initWithFrame:frame] autorelease]; 81 | [self addSubview:self.imageView]; 82 | } 83 | 84 | #pragma mark - Display Functions 85 | - (void)displayImage:(UIImage *)newImage withSenderView:(UIImageView*)senderView 86 | { 87 | CGSize frameSize = self.frame.size; 88 | CGSize imageSize = newImage.size; 89 | CGFloat xScale = frameSize.width / imageSize.width; // the scale needed to perfectly fit the image width-wise 90 | CGFloat yScale = frameSize.height / imageSize.height; // the scale needed to perfectly fit the image height-wise 91 | CGFloat minScale = MIN(xScale, yScale); // use minimum of these to allow the image to become fully visible 92 | CGRect photoImageViewFrame; 93 | 94 | if (xScale > 1 && yScale > 1) 95 | { 96 | photoImageViewFrame.origin = CGPointZero; 97 | photoImageViewFrame.size.height = newImage.size.height*minScale; 98 | photoImageViewFrame.size.width = newImage.size.width*minScale; 99 | [self imageView].frame = photoImageViewFrame; 100 | self.contentSize = photoImageViewFrame.size; 101 | [self imageView].image = newImage; 102 | minScale = 1; 103 | } 104 | else 105 | { 106 | photoImageViewFrame.origin = CGPointZero; 107 | photoImageViewFrame.size = newImage.size; 108 | [self imageView].frame = photoImageViewFrame; 109 | self.contentSize = photoImageViewFrame.size; 110 | [self imageView].image = newImage; 111 | } 112 | 113 | self.maximumZoomScale = 2.f*minScale;//maxScale; 114 | self.minimumZoomScale = minScale; 115 | self.zoomScale = minScale; 116 | 117 | _disableAutoLayout = NO; 118 | [self layoutIfNeeded]; // call layoutSubviews 119 | 120 | [self configAnimationDisplay:senderView]; 121 | } 122 | 123 | //Image in PhotoView cannot be zoom in without this function. 124 | - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView 125 | { 126 | return self.imageView; 127 | } 128 | 129 | - (BOOL)isZoomed 130 | { 131 | return !([self zoomScale] == [self minimumZoomScale]); 132 | } 133 | 134 | - (void)layoutSubviews 135 | { 136 | [super layoutSubviews]; 137 | 138 | if (_disableAutoLayout) return; 139 | 140 | // Center the image as it becomes smaller than the size of the screen 141 | CGSize boundsSize = [self bounds].size; 142 | CGRect frameToCenter = [self imageView].frame; 143 | 144 | // Horizontally 145 | if (frameToCenter.size.width < boundsSize.width) 146 | { 147 | frameToCenter.origin.x = floorf((boundsSize.width - frameToCenter.size.width) / 2.0); 148 | } 149 | else 150 | { 151 | frameToCenter.origin.x = 0; 152 | } 153 | 154 | // Vertically 155 | if (frameToCenter.size.height < boundsSize.height) 156 | { 157 | frameToCenter.origin.y = floorf((boundsSize.height - frameToCenter.size.height) / 2.0); 158 | } 159 | else 160 | { 161 | frameToCenter.origin.y = 0; 162 | } 163 | 164 | // Center 165 | if (!CGRectEqualToRect([self imageView].frame, frameToCenter)) 166 | { 167 | [self imageView].frame = frameToCenter; 168 | } 169 | } 170 | 171 | - (CGRect)zoomRectForScale:(float)scale withCenter:(CGPoint)center 172 | { 173 | CGRect zoomRect; 174 | 175 | zoomRect.size.height = self.frame.size.height / scale; 176 | zoomRect.size.width = self.frame.size.width / scale; 177 | zoomRect.origin.x = center.x/self.zoomScale - (zoomRect.size.width /2.0 ); 178 | zoomRect.origin.y = center.y/self.zoomScale - (zoomRect.size.height /2.0 ); 179 | 180 | zoomRect.origin.x -= (self.bounds.size.width - self.imageView.frame.size.width)/2/self.zoomScale; 181 | 182 | if (center.y < self.bounds.size.height/2 && self.imageView.frame.size.height < self.imageView.frame.size.width) 183 | { 184 | zoomRect.origin.y -= (self.bounds.size.height - self.imageView.frame.size.height)/self.zoomScale ; 185 | } 186 | else 187 | { 188 | zoomRect.origin.y -= (self.bounds.size.height - self.imageView.frame.size.height)/2/self.zoomScale; 189 | } 190 | return zoomRect; 191 | } 192 | 193 | #pragma mark - Config Animation Methods 194 | - (void)normalModeShowWithSenderView:(UIView*)senderView 195 | { 196 | UIImageView *oriImageView = (UIImageView *)senderView; 197 | CGRect curFrame = [self imageView].frame; 198 | 199 | CGRect oriFrame = [oriImageView.superview convertRect:oriImageView.frame toView:self]; 200 | self.senderFrame = oriFrame; 201 | [[self imageView] setFrame:oriFrame]; 202 | 203 | if (oriFrame.origin.y<44) 204 | { 205 | _disableAutoLayout = YES; 206 | 207 | self.offscreenY = (oriFrame.origin.y<0)?(fabs(oriFrame.origin.y)+44):(44-oriFrame.origin.y); 208 | [[self imageView] setFrame:CGRectMake(oriFrame.origin.x, -self.offscreenY, oriFrame.size.width, oriFrame.size.height)]; 209 | 210 | oriFrame.origin.y = 44.f; 211 | oriFrame.size.height -= self.offscreenY; 212 | 213 | CGRect clipRect = oriFrame; 214 | clipRect.origin.x = 0.f; 215 | clipRect.size.width = 320.f; 216 | UIView *clipsView = [[[UIView alloc] initWithFrame:clipRect] autorelease]; 217 | [clipsView setClipsToBounds:YES]; 218 | [clipsView setBackgroundColor:[UIColor clearColor]]; 219 | [[self imageView] removeFromSuperview]; 220 | [clipsView addSubview:self.imageView]; 221 | [self addSubview:clipsView]; 222 | 223 | [self setBackgroundColor:[UIColor clearColor]]; 224 | [UIView animateWithDuration:.3f delay:.0f options:UIViewAnimationOptionCurveEaseInOut animations:^{ 225 | [self setBackgroundColor:[[UIColor blackColor] colorWithAlphaComponent:0.95f]]; 226 | [clipsView setFrame:curFrame]; 227 | [self.imageView setFrame:CGRectMake((320-curFrame.size.width)/2, 0, curFrame.size.width, curFrame.size.height)]; 228 | } completion:^(BOOL finished) { 229 | _disableAutoLayout = NO; 230 | [self.imageView removeFromSuperview]; 231 | [self.imageView setFrame:curFrame]; 232 | [clipsView removeFromSuperview]; 233 | [self addSubview:self.imageView]; 234 | }]; 235 | } 236 | else 237 | { 238 | [self setBackgroundColor:[UIColor clearColor]]; 239 | [UIView animateWithDuration:.3f delay:.0f options:UIViewAnimationOptionCurveEaseInOut animations:^{ 240 | [self setBackgroundColor:[[UIColor blackColor] colorWithAlphaComponent:0.95f]]; 241 | [[self imageView] setFrame:curFrame]; 242 | } completion:^(BOOL finished) {}]; 243 | } 244 | } 245 | 246 | - (void)configAnimationDisplay:(UIImageView *)senderView 247 | { 248 | if (nil == senderView) return; 249 | 250 | self.offscreenY = 0.f; 251 | 252 | switch (senderView.contentMode) { 253 | case UIViewContentModeScaleAspectFit: 254 | ivContentMode = UIViewContentModeScaleAspectFit; 255 | [self aspectFillModeShowWithSenderView:senderView]; 256 | break; 257 | case UIViewContentModeScaleAspectFill: 258 | ivContentMode = UIViewContentModeScaleAspectFill; 259 | [self aspectFillModeShowWithSenderView:senderView]; 260 | break; 261 | default: 262 | ivContentMode = UIViewContentModeScaleToFill; 263 | [self normalModeShowWithSenderView:senderView]; 264 | break; 265 | } 266 | } 267 | 268 | - (void)aspectFillModeShowWithSenderView:(UIView*)senderView 269 | { 270 | UIImageView *oriImageView = (UIImageView *)senderView; 271 | CGRect curFrame = [self imageView].frame; 272 | 273 | _disableAutoLayout = YES; 274 | 275 | CGRect clipFrame = [senderView.superview convertRect:senderView.frame toView:self]; 276 | 277 | if (clipFrame.origin.y<44) 278 | { 279 | self.offscreenY = (clipFrame.origin.y<0)?(fabs(clipFrame.origin.y)+44):(44-clipFrame.origin.y); 280 | clipFrame.origin.y = 44.f; 281 | clipFrame.size.height -= self.offscreenY; 282 | } 283 | self.clipsFrame = clipFrame; 284 | 285 | UIView *clipsView = [[[UIView alloc] initWithFrame:clipFrame] autorelease]; 286 | [clipsView setClipsToBounds:YES]; 287 | [[self imageView] removeFromSuperview]; 288 | [clipsView addSubview:self.imageView]; 289 | [self addSubview:clipsView]; 290 | 291 | CGRect convertRect = senderView.frame; 292 | CGFloat hImageRate = oriImageView.image.size.height/oriImageView.image.size.width; 293 | CGFloat hViewRate = oriImageView.frame.size.height/oriImageView.frame.size.width; 294 | 295 | if ( (ivContentMode == UIViewContentModeScaleAspectFill && hImageRate > hViewRate) || 296 | (ivContentMode == UIViewContentModeScaleAspectFit && hImageRate < hViewRate) ) 297 | { 298 | convertRect.size.height = oriImageView.image.size.height*senderView.frame.size.width/oriImageView.image.size.width; 299 | convertRect.origin.y = senderView.frame.origin.y - (convertRect.size.height-senderView.frame.size.height)*0.5f; 300 | } 301 | else 302 | { 303 | convertRect.size.width = oriImageView.image.size.width*senderView.frame.size.height/oriImageView.image.size.height; 304 | convertRect.origin.x = senderView.frame.origin.x - (convertRect.size.width-senderView.frame.size.width)*0.5f; 305 | } 306 | 307 | CGRect oriFrame = [senderView.superview convertRect:convertRect toView:clipsView]; 308 | self.senderFrame = oriFrame; 309 | [[self imageView] setFrame:oriFrame]; 310 | 311 | [self setBackgroundColor:[UIColor clearColor]]; 312 | [UIView animateWithDuration:.3f delay:.0f options:UIViewAnimationOptionCurveEaseInOut animations:^{ 313 | [self setBackgroundColor:[[UIColor blackColor] colorWithAlphaComponent:0.95f]]; 314 | [clipsView setFrame:curFrame]; 315 | [self.imageView setFrame:CGRectMake(0, 0, curFrame.size.width, curFrame.size.height)]; 316 | } completion:^(BOOL finished) { 317 | _disableAutoLayout = NO; 318 | [self.imageView removeFromSuperview]; 319 | [self.imageView setFrame:curFrame]; 320 | [clipsView removeFromSuperview]; 321 | [self addSubview:self.imageView]; 322 | }]; 323 | } 324 | 325 | #pragma mark - PhotoView Clicked Methods 326 | - (void)zoomToLocation:(UITapGestureRecognizer *)tapGes 327 | { 328 | if ([self isZoomed]) 329 | { 330 | [self setZoomScale:self.minimumZoomScale animated:YES]; 331 | } 332 | else 333 | { 334 | CGRect zoomRect = [self zoomRectForScale:self.maximumZoomScale withCenter:[tapGes locationInView:self]]; 335 | [self zoomToRect:zoomRect animated:YES]; 336 | } 337 | } 338 | 339 | - (void)removePhotoView 340 | { 341 | _disableAutoLayout = YES; 342 | 343 | if (ivContentMode != UIViewContentModeScaleToFill) 344 | { 345 | UIView *clipView = [[[UIView alloc] initWithFrame:[self imageView].frame] autorelease]; 346 | [clipView setClipsToBounds:YES]; 347 | [self.imageView removeFromSuperview]; 348 | [self.imageView setFrame:CGRectMake(0, 0, CGRectGetWidth([self imageView].frame), CGRectGetHeight([self imageView].frame))]; 349 | [clipView addSubview:self.imageView]; 350 | [self addSubview:clipView]; 351 | 352 | [UIView animateWithDuration:0.3f animations:^{ 353 | [clipView setFrame:self.clipsFrame]; 354 | if ([self isZoomed]) 355 | { 356 | [self setContentOffset:CGPointMake(0.f, 0.f)]; 357 | } 358 | [[self imageView] setFrame:self.senderFrame]; 359 | }]; 360 | 361 | [UIView animateWithDuration:0.5f animations:^{ 362 | [self setBackgroundColor:[UIColor clearColor]]; 363 | } completion:^(BOOL finished) { 364 | [self removeFromSuperview]; 365 | if (_block) _block(YES); 366 | }]; 367 | } 368 | else 369 | { 370 | if (self.offscreenY != 0) 371 | { 372 | UIView *clipView = [[[UIView alloc] initWithFrame:[self imageView].frame] autorelease]; 373 | [clipView setClipsToBounds:YES]; 374 | [self.imageView removeFromSuperview]; 375 | [self.imageView setFrame:CGRectMake(0, 0, CGRectGetWidth([self imageView].frame), CGRectGetHeight([self imageView].frame))]; 376 | [clipView addSubview:self.imageView]; 377 | [self addSubview:clipView]; 378 | 379 | CGRect clipOriFrame = self.imageView.frame; 380 | clipOriFrame.origin.y = 44.f; 381 | clipOriFrame.size.height -= self.offscreenY; 382 | 383 | [UIView animateWithDuration:0.3f animations:^{ 384 | [clipView setFrame:clipOriFrame]; 385 | if ([self isZoomed]) 386 | { 387 | [self setContentOffset:CGPointMake(0.f, 0.f)]; 388 | } 389 | [[self imageView] setFrame:CGRectMake(self.senderFrame.origin.x, -self.offscreenY, self.senderFrame.size.width, self.senderFrame.size.height)]; 390 | }]; 391 | 392 | [UIView animateWithDuration:.5f animations:^{ 393 | [self setBackgroundColor:[UIColor clearColor]]; 394 | } completion:^(BOOL finished) { 395 | [self removeFromSuperview]; 396 | if (_block) _block(YES); 397 | }]; 398 | } 399 | else 400 | { 401 | [UIView animateWithDuration:0.3f animations:^{ 402 | if ([self isZoomed]) 403 | { 404 | [self setContentOffset:CGPointMake(0.f, 0.f)]; 405 | } 406 | [[self imageView] setFrame:self.senderFrame]; 407 | }]; 408 | 409 | [UIView animateWithDuration:.5f animations:^{ 410 | [self setBackgroundColor:[UIColor clearColor]]; 411 | } completion:^(BOOL finished) { 412 | [self removeFromSuperview]; 413 | if (_block) _block(YES); 414 | }]; 415 | } 416 | } 417 | } 418 | 419 | - (void)dealloc 420 | { 421 | self.imageView = nil; 422 | [super dealloc]; 423 | } 424 | 425 | @end 426 | -------------------------------------------------------------------------------- /FSPhotoViewDemo/FSPhotoViewDemo/FSPhotoView/FSPhotoView.m: -------------------------------------------------------------------------------- 1 | // 2 | // FSPhotoView.m 3 | // FSPhotoView 4 | // 5 | // Created by Stephen Jin on 7/26/13. 6 | // Copyright (c) 2013 Stephen Jin. All rights reserved. 7 | // 8 | 9 | #import "FSPhotoView.h" 10 | 11 | @interface FSPhotoView () { 12 | BOOL _disableAutoLayout; 13 | UIViewContentMode ivContentMode; 14 | } 15 | @property (nonatomic, retain) UIImageView *imageView; 16 | @property (nonatomic, assign) CGRect senderFrame; 17 | @property (nonatomic, assign) CGRect clipsFrame; 18 | @property (nonatomic, assign) CGFloat offscreenY; 19 | @property (copy) DisappearBlcok block; 20 | 21 | - (void)displayImage:(UIImage *)newImage withSenderView:(UIImageView*)senderView; 22 | @end 23 | 24 | @implementation FSPhotoView 25 | 26 | + (FSPhotoView *)showImageWithSenderView:(UIImageView*)senderView; 27 | { 28 | if (nil == senderView.image) return nil; 29 | return [FSPhotoView showImageWithSenderView:senderView completion:nil]; 30 | } 31 | 32 | + (FSPhotoView *)showImageWithSenderView:(UIImageView*)senderView completion:(DisappearBlcok)completed; 33 | { 34 | if (nil == senderView.image) return nil; 35 | 36 | // Deal with staus bar. 37 | CGRect rect = [UIScreen mainScreen].bounds; 38 | 39 | if (![UIApplication sharedApplication].statusBarHidden) 40 | { 41 | rect.size.height = [UIScreen mainScreen].bounds.size.height-20.f; 42 | rect.origin.y += [UIApplication sharedApplication].statusBarHidden?0:20.f; 43 | } 44 | 45 | FSPhotoView *photoView = [[FSPhotoView alloc] initWithFrame:rect andCompleteBlock:completed]; 46 | if (photoView) 47 | { 48 | [[UIApplication sharedApplication].keyWindow addSubview:photoView]; 49 | [photoView displayImage:senderView.image withSenderView:senderView]; 50 | } 51 | return [photoView autorelease]; 52 | } 53 | 54 | - (id)initWithFrame:(CGRect)frame andCompleteBlock:(DisappearBlcok)block 55 | { 56 | self = [super initWithFrame:frame]; 57 | if (self) { 58 | // Initialization code 59 | self.block = block; 60 | [self setDelegate:self]; 61 | [self setMaximumZoomScale:2.f]; 62 | [self setShowsHorizontalScrollIndicator:NO]; 63 | [self setShowsVerticalScrollIndicator:NO]; 64 | [self loadSubviewsWithFrame:frame]; 65 | UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(removePhotoView)]; 66 | [singleTap setNumberOfTapsRequired:1]; 67 | [self addGestureRecognizer:singleTap]; 68 | [singleTap release]; 69 | UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(zoomToLocation:)]; 70 | [doubleTap setNumberOfTapsRequired:2]; 71 | [self addGestureRecognizer:doubleTap]; 72 | [singleTap requireGestureRecognizerToFail:doubleTap]; 73 | [doubleTap release]; 74 | } 75 | return self; 76 | } 77 | 78 | - (void)loadSubviewsWithFrame:(CGRect)frame 79 | { 80 | self.imageView = [[[UIImageView alloc] initWithFrame:frame] autorelease]; 81 | [self addSubview:self.imageView]; 82 | } 83 | 84 | #pragma mark - Display Functions 85 | - (void)displayImage:(UIImage *)newImage withSenderView:(UIImageView*)senderView 86 | { 87 | CGSize frameSize = self.frame.size; 88 | CGSize imageSize = newImage.size; 89 | CGFloat xScale = frameSize.width / imageSize.width; // the scale needed to perfectly fit the image width-wise 90 | CGFloat yScale = frameSize.height / imageSize.height; // the scale needed to perfectly fit the image height-wise 91 | CGFloat minScale = MIN(xScale, yScale); // use minimum of these to allow the image to become fully visible 92 | CGRect photoImageViewFrame; 93 | 94 | if (xScale > 1 && yScale > 1) 95 | { 96 | photoImageViewFrame.origin = CGPointZero; 97 | photoImageViewFrame.size.height = newImage.size.height*minScale; 98 | photoImageViewFrame.size.width = newImage.size.width*minScale; 99 | [self imageView].frame = photoImageViewFrame; 100 | self.contentSize = photoImageViewFrame.size; 101 | [self imageView].image = newImage; 102 | minScale = 1; 103 | } 104 | else 105 | { 106 | photoImageViewFrame.origin = CGPointZero; 107 | photoImageViewFrame.size = newImage.size; 108 | [self imageView].frame = photoImageViewFrame; 109 | self.contentSize = photoImageViewFrame.size; 110 | [self imageView].image = newImage; 111 | } 112 | 113 | self.maximumZoomScale = 2.f*minScale;//maxScale; 114 | self.minimumZoomScale = minScale; 115 | self.zoomScale = minScale; 116 | 117 | _disableAutoLayout = NO; 118 | [self layoutIfNeeded]; // call layoutSubviews 119 | 120 | [self configAnimationDisplay:senderView]; 121 | } 122 | 123 | //Image in PhotoView cannot be zoom in without this function. 124 | - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView 125 | { 126 | return self.imageView; 127 | } 128 | 129 | - (BOOL)isZoomed 130 | { 131 | return !([self zoomScale] == [self minimumZoomScale]); 132 | } 133 | 134 | - (void)layoutSubviews 135 | { 136 | [super layoutSubviews]; 137 | 138 | if (_disableAutoLayout) return; 139 | 140 | // Center the image as it becomes smaller than the size of the screen 141 | CGSize boundsSize = [self bounds].size; 142 | CGRect frameToCenter = [self imageView].frame; 143 | 144 | // Horizontally 145 | if (frameToCenter.size.width < boundsSize.width) 146 | { 147 | frameToCenter.origin.x = floorf((boundsSize.width - frameToCenter.size.width) / 2.0); 148 | } 149 | else 150 | { 151 | frameToCenter.origin.x = 0; 152 | } 153 | 154 | // Vertically 155 | if (frameToCenter.size.height < boundsSize.height) 156 | { 157 | frameToCenter.origin.y = floorf((boundsSize.height - frameToCenter.size.height) / 2.0); 158 | } 159 | else 160 | { 161 | frameToCenter.origin.y = 0; 162 | } 163 | 164 | // Center 165 | if (!CGRectEqualToRect([self imageView].frame, frameToCenter)) 166 | { 167 | [self imageView].frame = frameToCenter; 168 | } 169 | } 170 | 171 | - (CGRect)zoomRectForScale:(float)scale withCenter:(CGPoint)center 172 | { 173 | CGRect zoomRect; 174 | 175 | zoomRect.size.height = self.frame.size.height / scale; 176 | zoomRect.size.width = self.frame.size.width / scale; 177 | zoomRect.origin.x = center.x/self.zoomScale - (zoomRect.size.width /2.0 ); 178 | zoomRect.origin.y = center.y/self.zoomScale - (zoomRect.size.height /2.0 ); 179 | 180 | zoomRect.origin.x -= (self.bounds.size.width - self.imageView.frame.size.width)/2/self.zoomScale; 181 | 182 | if (center.y < self.bounds.size.height/2 && self.imageView.frame.size.height < self.imageView.frame.size.width) 183 | { 184 | zoomRect.origin.y -= (self.bounds.size.height - self.imageView.frame.size.height)/self.zoomScale ; 185 | } 186 | else 187 | { 188 | zoomRect.origin.y -= (self.bounds.size.height - self.imageView.frame.size.height)/2/self.zoomScale; 189 | } 190 | return zoomRect; 191 | } 192 | 193 | #pragma mark - Config Animation Methods 194 | - (void)normalModeShowWithSenderView:(UIView*)senderView 195 | { 196 | UIImageView *oriImageView = (UIImageView *)senderView; 197 | CGRect curFrame = [self imageView].frame; 198 | 199 | CGRect oriFrame = [oriImageView.superview convertRect:oriImageView.frame toView:self]; 200 | self.senderFrame = oriFrame; 201 | [[self imageView] setFrame:oriFrame]; 202 | 203 | if (oriFrame.origin.y<44) 204 | { 205 | _disableAutoLayout = YES; 206 | 207 | self.offscreenY = (oriFrame.origin.y<0)?(fabs(oriFrame.origin.y)+44):(44-oriFrame.origin.y); 208 | [[self imageView] setFrame:CGRectMake(oriFrame.origin.x, -self.offscreenY, oriFrame.size.width, oriFrame.size.height)]; 209 | 210 | oriFrame.origin.y = 44.f; 211 | oriFrame.size.height -= self.offscreenY; 212 | 213 | CGRect clipRect = oriFrame; 214 | clipRect.origin.x = 0.f; 215 | clipRect.size.width = 320.f; 216 | UIView *clipsView = [[[UIView alloc] initWithFrame:clipRect] autorelease]; 217 | [clipsView setClipsToBounds:YES]; 218 | [clipsView setBackgroundColor:[UIColor clearColor]]; 219 | [[self imageView] removeFromSuperview]; 220 | [clipsView addSubview:self.imageView]; 221 | [self addSubview:clipsView]; 222 | 223 | [self setBackgroundColor:[UIColor clearColor]]; 224 | [UIView animateWithDuration:.3f delay:.0f options:UIViewAnimationOptionCurveEaseInOut animations:^{ 225 | [self setBackgroundColor:[[UIColor blackColor] colorWithAlphaComponent:0.95f]]; 226 | [clipsView setFrame:curFrame]; 227 | [self.imageView setFrame:CGRectMake((320-curFrame.size.width)/2, 0, curFrame.size.width, curFrame.size.height)]; 228 | } completion:^(BOOL finished) { 229 | _disableAutoLayout = NO; 230 | [self.imageView removeFromSuperview]; 231 | [self.imageView setFrame:curFrame]; 232 | [clipsView removeFromSuperview]; 233 | [self addSubview:self.imageView]; 234 | }]; 235 | } 236 | else 237 | { 238 | [self setBackgroundColor:[UIColor clearColor]]; 239 | [UIView animateWithDuration:.3f delay:.0f options:UIViewAnimationOptionCurveEaseInOut animations:^{ 240 | [self setBackgroundColor:[[UIColor blackColor] colorWithAlphaComponent:0.95f]]; 241 | [[self imageView] setFrame:curFrame]; 242 | } completion:^(BOOL finished) {}]; 243 | } 244 | } 245 | 246 | - (void)configAnimationDisplay:(UIImageView *)senderView 247 | { 248 | if (nil == senderView) return; 249 | 250 | self.offscreenY = 0.f; 251 | 252 | switch (senderView.contentMode) { 253 | case UIViewContentModeScaleAspectFit: 254 | ivContentMode = UIViewContentModeScaleAspectFit; 255 | [self aspectFillModeShowWithSenderView:senderView]; 256 | break; 257 | case UIViewContentModeScaleAspectFill: 258 | ivContentMode = UIViewContentModeScaleAspectFill; 259 | [self aspectFillModeShowWithSenderView:senderView]; 260 | break; 261 | default: 262 | ivContentMode = UIViewContentModeScaleToFill; 263 | [self normalModeShowWithSenderView:senderView]; 264 | break; 265 | } 266 | } 267 | 268 | - (void)aspectFillModeShowWithSenderView:(UIView*)senderView 269 | { 270 | UIImageView *oriImageView = (UIImageView *)senderView; 271 | CGRect curFrame = [self imageView].frame; 272 | 273 | _disableAutoLayout = YES; 274 | 275 | CGRect clipFrame = [senderView.superview convertRect:senderView.frame toView:self]; 276 | 277 | if (clipFrame.origin.y<44) 278 | { 279 | self.offscreenY = (clipFrame.origin.y<0)?(fabs(clipFrame.origin.y)+44):(44-clipFrame.origin.y); 280 | clipFrame.origin.y = 44.f; 281 | clipFrame.size.height -= self.offscreenY; 282 | } 283 | self.clipsFrame = clipFrame; 284 | 285 | UIView *clipsView = [[[UIView alloc] initWithFrame:clipFrame] autorelease]; 286 | [clipsView setClipsToBounds:YES]; 287 | [[self imageView] removeFromSuperview]; 288 | [clipsView addSubview:self.imageView]; 289 | [self addSubview:clipsView]; 290 | 291 | CGRect convertRect = senderView.frame; 292 | CGFloat hImageRate = oriImageView.image.size.height/oriImageView.image.size.width; 293 | CGFloat hViewRate = oriImageView.frame.size.height/oriImageView.frame.size.width; 294 | 295 | if ( (ivContentMode == UIViewContentModeScaleAspectFill && hImageRate > hViewRate) || 296 | (ivContentMode == UIViewContentModeScaleAspectFit && hImageRate < hViewRate) ) 297 | { 298 | convertRect.size.height = oriImageView.image.size.height*senderView.frame.size.width/oriImageView.image.size.width; 299 | convertRect.origin.y = senderView.frame.origin.y - (convertRect.size.height-senderView.frame.size.height)*0.5f; 300 | } 301 | else 302 | { 303 | convertRect.size.width = oriImageView.image.size.width*senderView.frame.size.height/oriImageView.image.size.height; 304 | convertRect.origin.x = senderView.frame.origin.x - (convertRect.size.width-senderView.frame.size.width)*0.5f; 305 | } 306 | 307 | CGRect oriFrame = [senderView.superview convertRect:convertRect toView:clipsView]; 308 | self.senderFrame = oriFrame; 309 | [[self imageView] setFrame:oriFrame]; 310 | 311 | [self setBackgroundColor:[UIColor clearColor]]; 312 | [UIView animateWithDuration:.3f delay:.0f options:UIViewAnimationOptionCurveEaseInOut animations:^{ 313 | [self setBackgroundColor:[[UIColor blackColor] colorWithAlphaComponent:0.95f]]; 314 | [clipsView setFrame:curFrame]; 315 | [self.imageView setFrame:CGRectMake(0, 0, curFrame.size.width, curFrame.size.height)]; 316 | } completion:^(BOOL finished) { 317 | _disableAutoLayout = NO; 318 | [self.imageView removeFromSuperview]; 319 | [self.imageView setFrame:curFrame]; 320 | [clipsView removeFromSuperview]; 321 | [self addSubview:self.imageView]; 322 | }]; 323 | } 324 | 325 | #pragma mark - PhotoView Clicked Methods 326 | - (void)zoomToLocation:(UITapGestureRecognizer *)tapGes 327 | { 328 | if ([self isZoomed]) 329 | { 330 | [self setZoomScale:self.minimumZoomScale animated:YES]; 331 | } 332 | else 333 | { 334 | CGRect zoomRect = [self zoomRectForScale:self.maximumZoomScale withCenter:[tapGes locationInView:self]]; 335 | [self zoomToRect:zoomRect animated:YES]; 336 | } 337 | } 338 | 339 | - (void)removePhotoView 340 | { 341 | _disableAutoLayout = YES; 342 | 343 | if (ivContentMode != UIViewContentModeScaleToFill) 344 | { 345 | UIView *clipView = [[[UIView alloc] initWithFrame:[self imageView].frame] autorelease]; 346 | [clipView setClipsToBounds:YES]; 347 | [self.imageView removeFromSuperview]; 348 | [self.imageView setFrame:CGRectMake(0, 0, CGRectGetWidth([self imageView].frame), CGRectGetHeight([self imageView].frame))]; 349 | [clipView addSubview:self.imageView]; 350 | [self addSubview:clipView]; 351 | 352 | [UIView animateWithDuration:0.3f animations:^{ 353 | [clipView setFrame:self.clipsFrame]; 354 | if ([self isZoomed]) 355 | { 356 | [self setContentOffset:CGPointMake(0.f, 0.f)]; 357 | } 358 | [[self imageView] setFrame:self.senderFrame]; 359 | }]; 360 | 361 | [UIView animateWithDuration:0.5f animations:^{ 362 | [self setBackgroundColor:[UIColor clearColor]]; 363 | } completion:^(BOOL finished) { 364 | [self removeFromSuperview]; 365 | if (_block) _block(YES); 366 | }]; 367 | } 368 | else 369 | { 370 | if (self.offscreenY != 0) 371 | { 372 | UIView *clipView = [[[UIView alloc] initWithFrame:[self imageView].frame] autorelease]; 373 | [clipView setClipsToBounds:YES]; 374 | [self.imageView removeFromSuperview]; 375 | [self.imageView setFrame:CGRectMake(0, 0, CGRectGetWidth([self imageView].frame), CGRectGetHeight([self imageView].frame))]; 376 | [clipView addSubview:self.imageView]; 377 | [self addSubview:clipView]; 378 | 379 | CGRect clipOriFrame = self.imageView.frame; 380 | clipOriFrame.origin.y = 44.f; 381 | clipOriFrame.size.height -= self.offscreenY; 382 | 383 | [UIView animateWithDuration:0.3f animations:^{ 384 | [clipView setFrame:clipOriFrame]; 385 | if ([self isZoomed]) 386 | { 387 | [self setContentOffset:CGPointMake(0.f, 0.f)]; 388 | } 389 | [[self imageView] setFrame:CGRectMake(self.senderFrame.origin.x, -self.offscreenY, self.senderFrame.size.width, self.senderFrame.size.height)]; 390 | }]; 391 | 392 | [UIView animateWithDuration:.5f animations:^{ 393 | [self setBackgroundColor:[UIColor clearColor]]; 394 | } completion:^(BOOL finished) { 395 | [self removeFromSuperview]; 396 | if (_block) _block(YES); 397 | }]; 398 | } 399 | else 400 | { 401 | [UIView animateWithDuration:0.3f animations:^{ 402 | if ([self isZoomed]) 403 | { 404 | [self setContentOffset:CGPointMake(0.f, 0.f)]; 405 | } 406 | [[self imageView] setFrame:self.senderFrame]; 407 | }]; 408 | 409 | [UIView animateWithDuration:.5f animations:^{ 410 | [self setBackgroundColor:[UIColor clearColor]]; 411 | } completion:^(BOOL finished) { 412 | [self removeFromSuperview]; 413 | if (_block) _block(YES); 414 | }]; 415 | } 416 | } 417 | } 418 | 419 | - (void)dealloc 420 | { 421 | self.imageView = nil; 422 | [super dealloc]; 423 | } 424 | 425 | @end 426 | --------------------------------------------------------------------------------