From e1ec1d9a777ea9954b72ee1050f8aa381a5296d6 Mon Sep 17 00:00:00 2001 From: Vito Date: Sat, 10 Jan 2015 11:40:17 +0800 Subject: [PATCH] Update file directory --- VIPhotoView/VIPhotoView.h | 15 ++ VIPhotoView/VIPhotoView.m | 216 ++++++++++++++++++++++ VIPhotoViewDemo.xcodeproj/project.pbxproj | 30 +-- 3 files changed, 247 insertions(+), 14 deletions(-) create mode 100644 VIPhotoView/VIPhotoView.h create mode 100644 VIPhotoView/VIPhotoView.m diff --git a/VIPhotoView/VIPhotoView.h b/VIPhotoView/VIPhotoView.h new file mode 100644 index 0000000..257a268 --- /dev/null +++ b/VIPhotoView/VIPhotoView.h @@ -0,0 +1,15 @@ +// +// VIPhotoView.h +// VIPhotoViewDemo +// +// Created by Vito on 1/7/15. +// Copyright (c) 2015 vito. All rights reserved. +// + +#import + +@interface VIPhotoView : UIScrollView + +- (instancetype)initWithFrame:(CGRect)frame andImage:(UIImage *)image; + +@end diff --git a/VIPhotoView/VIPhotoView.m b/VIPhotoView/VIPhotoView.m new file mode 100644 index 0000000..12dfb9e --- /dev/null +++ b/VIPhotoView/VIPhotoView.m @@ -0,0 +1,216 @@ +// +// VIPhotoView.m +// VIPhotoViewDemo +// +// Created by Vito on 1/7/15. +// Copyright (c) 2015 vito. All rights reserved. +// + +#import "VIPhotoView.h" + +@interface UIImage (VIUtil) + +- (CGSize)sizeThatFits:(CGSize)size; + +@end + +@implementation UIImage (VIUtil) + +- (CGSize)sizeThatFits:(CGSize)size +{ + CGSize imageSize = CGSizeMake(self.size.width / self.scale, + self.size.height / self.scale); + + CGFloat widthRatio = imageSize.width / size.width; + CGFloat heightRatio = imageSize.height / size.height; + + if (widthRatio > heightRatio) { + imageSize = CGSizeMake(imageSize.width / widthRatio, imageSize.height / widthRatio); + } else { + imageSize = CGSizeMake(imageSize.width / heightRatio, imageSize.height / heightRatio); + } + + return imageSize; +} + +@end + +@interface UIImageView (VIUtil) + +- (CGSize)contentSize; + +@end + +@implementation UIImageView (VIUtil) + +- (CGSize)contentSize +{ + return [self.image sizeThatFits:self.bounds.size]; +} + +@end + +@interface VIPhotoView () + +@property (nonatomic, strong) UIView *containerView; +@property (nonatomic, strong) UIImageView *imageView; + +@property (nonatomic) BOOL rotating; +@property (nonatomic) CGSize minSize; + +@end + +@implementation VIPhotoView + + +- (instancetype)initWithFrame:(CGRect)frame andImage:(UIImage *)image +{ + self = [super initWithFrame:frame]; + if (self) { + self.delegate = self; + self.bouncesZoom = YES; + + // Add container view + UIView *containerView = [[UIView alloc] initWithFrame:self.bounds]; + containerView.backgroundColor = [UIColor clearColor]; + [self addSubview:containerView]; + _containerView = containerView; + + // Add image view + UIImageView *imageView = [[UIImageView alloc] initWithImage:image]; + imageView.frame = containerView.bounds; + imageView.contentMode = UIViewContentModeScaleAspectFit; + [containerView addSubview:imageView]; + _imageView = imageView; + + // Fit container view's size to image size + CGSize imageSize = imageView.contentSize; + self.containerView.frame = CGRectMake(0, 0, imageSize.width, imageSize.height); + imageView.bounds = CGRectMake(0, 0, imageSize.width, imageSize.height); + imageView.center = CGPointMake(imageSize.width / 2, imageSize.height / 2); + + self.contentSize = imageSize; + self.minSize = imageSize; + + + [self setMaxMinZoomScale]; + + // Center containerView by set insets + [self centerContent]; + + // Setup other events + [self setupGestureRecognizer]; + [self setupRotationNotification]; + } + + return self; +} + +- (void)layoutSubviews +{ + [super layoutSubviews]; + + if (self.rotating) { + self.rotating = NO; + + // update container view frame + CGSize containerSize = self.containerView.frame.size; + BOOL containerSmallerThanSelf = (containerSize.width < CGRectGetWidth(self.bounds)) && (containerSize.height < CGRectGetHeight(self.bounds)); + + CGSize imageSize = [self.imageView.image sizeThatFits:self.bounds.size]; + CGFloat minZoomScale = imageSize.width / self.minSize.width; + self.minimumZoomScale = minZoomScale; + if (containerSmallerThanSelf || self.zoomScale == self.minimumZoomScale) { // 宽度或高度 都小于 self 的宽度和高度 + self.zoomScale = minZoomScale; + } + + // Center container view + [self centerContent]; + } +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +#pragma mark - Setup + +- (void)setupRotationNotification +{ + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(orientationChanged:) + name:UIApplicationDidChangeStatusBarOrientationNotification + object:nil]; +} + +- (void)setupGestureRecognizer +{ + UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapHandler:)]; + tapGestureRecognizer.numberOfTapsRequired = 2; + [_containerView addGestureRecognizer:tapGestureRecognizer]; +} + +#pragma mark - UIScrollViewDelegate + +- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView +{ + return self.containerView; +} + +- (void)scrollViewDidZoom:(UIScrollView *)scrollView +{ + [self centerContent]; +} + +#pragma mark - GestureRecognizer + +- (void)tapHandler:(UITapGestureRecognizer *)recognizer +{ + if (self.zoomScale > self.minimumZoomScale) { + [self setZoomScale:self.minimumZoomScale animated:YES]; + } else if (self.zoomScale < self.maximumZoomScale) { + CGPoint location = [recognizer locationInView:recognizer.view]; + CGRect zoomToRect = CGRectMake(0, 0, 50, 50); + zoomToRect.origin = CGPointMake(location.x - CGRectGetWidth(zoomToRect)/2, location.y - CGRectGetHeight(zoomToRect)/2); + [self zoomToRect:zoomToRect animated:YES]; + } +} + +#pragma mark - Notification + +- (void)orientationChanged:(NSNotification *)notification +{ + self.rotating = YES; +} + +#pragma mark - Helper + +- (void)setMaxMinZoomScale +{ + CGSize imageSize = self.imageView.image.size; + CGSize imagePresentationSize = self.imageView.contentSize; + CGFloat maxScale = MAX(imageSize.height / imagePresentationSize.height, imageSize.width / imagePresentationSize.width); + self.maximumZoomScale = MAX(1, maxScale); // Should not less than 1 + self.minimumZoomScale = 1.0; +} + +- (void)centerContent +{ + CGRect frame = self.containerView.frame; + + CGFloat top = 0, left = 0; + if (self.contentSize.width < self.bounds.size.width) { + left = (self.bounds.size.width - self.contentSize.width) * 0.5f; + } + if (self.contentSize.height < self.bounds.size.height) { + top = (self.bounds.size.height - self.contentSize.height) * 0.5f; + } + + top -= frame.origin.y; + left -= frame.origin.x; + + self.contentInset = UIEdgeInsetsMake(top, left, top, left); +} + +@end diff --git a/VIPhotoViewDemo.xcodeproj/project.pbxproj b/VIPhotoViewDemo.xcodeproj/project.pbxproj index b1df9c3..d6394bc 100644 --- a/VIPhotoViewDemo.xcodeproj/project.pbxproj +++ b/VIPhotoViewDemo.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 5F0A786B1A60D672006FB7E6 /* VIPhotoView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F0A786A1A60D672006FB7E6 /* VIPhotoView.m */; }; 5F856E071A5D791C0011786E /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F856E061A5D791C0011786E /* main.m */; }; 5F856E0A1A5D791C0011786E /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F856E091A5D791C0011786E /* AppDelegate.m */; }; 5F856E0D1A5D791C0011786E /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F856E0C1A5D791C0011786E /* ViewController.m */; }; @@ -14,7 +15,6 @@ 5F856E121A5D791C0011786E /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5F856E111A5D791C0011786E /* Images.xcassets */; }; 5F856E151A5D791C0011786E /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5F856E131A5D791C0011786E /* LaunchScreen.xib */; }; 5F856E211A5D791C0011786E /* VIPhotoViewDemoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F856E201A5D791C0011786E /* VIPhotoViewDemoTests.m */; }; - 5F856E2D1A5D794C0011786E /* VIPhotoView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F856E2C1A5D794C0011786E /* VIPhotoView.m */; }; 5F856E2F1A5D7E290011786E /* test.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 5F856E2E1A5D7E290011786E /* test.jpg */; }; /* End PBXBuildFile section */ @@ -29,6 +29,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 5F0A78691A60D672006FB7E6 /* VIPhotoView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VIPhotoView.h; sourceTree = ""; }; + 5F0A786A1A60D672006FB7E6 /* VIPhotoView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VIPhotoView.m; sourceTree = ""; }; 5F856E011A5D791C0011786E /* VIPhotoViewDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = VIPhotoViewDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 5F856E051A5D791C0011786E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 5F856E061A5D791C0011786E /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; @@ -42,8 +44,6 @@ 5F856E1A1A5D791C0011786E /* VIPhotoViewDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = VIPhotoViewDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 5F856E1F1A5D791C0011786E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 5F856E201A5D791C0011786E /* VIPhotoViewDemoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VIPhotoViewDemoTests.m; sourceTree = ""; }; - 5F856E2B1A5D794C0011786E /* VIPhotoView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VIPhotoView.h; sourceTree = ""; }; - 5F856E2C1A5D794C0011786E /* VIPhotoView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VIPhotoView.m; sourceTree = ""; }; 5F856E2E1A5D7E290011786E /* test.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = test.jpg; sourceTree = ""; }; /* End PBXFileReference section */ @@ -65,6 +65,15 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 5F0A78681A60D672006FB7E6 /* VIPhotoView */ = { + isa = PBXGroup; + children = ( + 5F0A78691A60D672006FB7E6 /* VIPhotoView.h */, + 5F0A786A1A60D672006FB7E6 /* VIPhotoView.m */, + ); + path = VIPhotoView; + sourceTree = ""; + }; 5F856DF81A5D791C0011786E = { isa = PBXGroup; children = ( @@ -86,7 +95,7 @@ 5F856E031A5D791C0011786E /* VIPhotoViewDemo */ = { isa = PBXGroup; children = ( - 5F856E2A1A5D79300011786E /* VIPhotoView */, + 5F0A78681A60D672006FB7E6 /* VIPhotoView */, 5F856E081A5D791C0011786E /* AppDelegate.h */, 5F856E091A5D791C0011786E /* AppDelegate.m */, 5F856E0B1A5D791C0011786E /* ViewController.h */, @@ -126,15 +135,6 @@ name = "Supporting Files"; sourceTree = ""; }; - 5F856E2A1A5D79300011786E /* VIPhotoView */ = { - isa = PBXGroup; - children = ( - 5F856E2B1A5D794C0011786E /* VIPhotoView.h */, - 5F856E2C1A5D794C0011786E /* VIPhotoView.m */, - ); - path = VIPhotoView; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -236,7 +236,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 5F856E2D1A5D794C0011786E /* VIPhotoView.m in Sources */, + 5F0A786B1A60D672006FB7E6 /* VIPhotoView.m in Sources */, 5F856E0D1A5D791C0011786E /* ViewController.m in Sources */, 5F856E0A1A5D791C0011786E /* AppDelegate.m in Sources */, 5F856E071A5D791C0011786E /* main.m in Sources */, @@ -432,6 +432,7 @@ 5F856E261A5D791C0011786E /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; 5F856E271A5D791C0011786E /* Build configuration list for PBXNativeTarget "VIPhotoViewDemoTests" */ = { isa = XCConfigurationList; @@ -440,6 +441,7 @@ 5F856E291A5D791C0011786E /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; /* End XCConfigurationList section */ };