diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..af884f8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# Created by https://www.gitignore.io/api/xcode + +### Xcode ### +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## Build generated +build/ +DerivedData/ + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ + +## Other +*.moved-aside +*.xccheckout +*.xcscmblueprint + diff --git a/README.md b/README.md index 8751dde..bbdd132 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,26 @@ userResizableView.delegate = self; Then implement the following delegate methods. ``` objective-c +/** + * Called when the resizable view receives touchesBegan: and activates the editing handles. + * + * @param userResizableView + */ - (void)userResizableViewDidBeginEditing:(SPUserResizableView *)userResizableView; + +/** + * Called when the resizable view receives touchesEnded: or touchesCancelled: + * + * @param userResizableView + */ - (void)userResizableViewDidEndEditing:(SPUserResizableView *)userResizableView; + +/** + * Called when new frame was set. + * + * @param userResizableView + */ +- (void)userResizableViewNewRealFrame:(SPUserResizableView *)userResizableView; ``` By default, SPUserResizableView will show the editing handles (as seen in the screenshot above) whenever it receives a touch event. The editing handles will remain visible even after the userResizableViewDidEndEditing: message is sent. This is to provide visual feedback to the user that the view is indeed moveable and resizable. If you'd like to dismiss the editing handles, you must explicitly call -hideEditingHandles. @@ -53,9 +71,47 @@ By default, SPUserResizableView will show the editing handles (as seen in the sc The SPUserResizableView is customizable using the following properties: ``` objective-c +/** + * Minimum width to let user resize + * @default Default is 48.0 for each. + */ @property (nonatomic) CGFloat minWidth; +/** + * Minimum height that will let user to resize + */ @property (nonatomic) CGFloat minHeight; + +/** + * Disables resize of the view + * @default NO + */ +@property (nonatomic) BOOL disable; + +/** + * Disables resize of the view if user use more than 1 finger. + * @default NO + */ +@property (nonatomic) BOOL disableOnMultiTouch; +/** + * Defaults to YES. Disables the user from dragging the view outside the parent view's bounds. + */ @property (nonatomic) BOOL preventsPositionOutsideSuperview; + +/** + * Defines if pan is disabled. + * @default NO + */ +@property (nonatomic) BOOL disablePan; + +/** + * Defines the insent of the content. + * Larger == better detection + */ +@property (nonatomic) float resizableInset; +/** + * Interactive border size + */ +@property (nonatomic) float interactiveBorderSize; ``` For an example of how to use SPUserResizableView, please see the included example project. diff --git a/SPUserResizableView+Pion.podspec b/SPUserResizableView+Pion.podspec new file mode 100644 index 0000000..fbabb2a --- /dev/null +++ b/SPUserResizableView+Pion.podspec @@ -0,0 +1,17 @@ +Pod::Spec.new do |s| + s.name = 'SPUserResizableView+Pion' + s.version = '0.5.4' + s.license = 'MIT' + s.homepage = 'https://github.com/pionl/SPUserResizableView' + s.authors = 'Stephen Poletto' + s.summary = 'A forked (originaly from STephen Poletto) SPUserResizableView is a user-resizable, user-repositionable UIView subclass. ' + +# Source Info + s.platform = :ios, '5.0' + s.source = {:git => 'https://github.com/pionl/SPUserResizableView.git', :tag => '0.5.4'} + s.source_files = 'SPUserResizableView/SPUserResizableView.{h,m}' + s.requires_arc = true + +# Pod Dependencies + +end diff --git a/SPUserResizableView.xcodeproj/project.pbxproj b/SPUserResizableView.xcodeproj/project.pbxproj index 2d25834..85f344d 100644 --- a/SPUserResizableView.xcodeproj/project.pbxproj +++ b/SPUserResizableView.xcodeproj/project.pbxproj @@ -13,9 +13,9 @@ 9C36DD441494491A00D3035F /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9C36DD421494491A00D3035F /* InfoPlist.strings */; }; 9C36DD461494491B00D3035F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C36DD451494491B00D3035F /* main.m */; }; 9C36DD4A1494491B00D3035F /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C36DD491494491B00D3035F /* AppDelegate.m */; }; - 9C36DD5414944A8F00D3035F /* SPUserResizableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C36DD5114944A8F00D3035F /* SPUserResizableView.m */; }; 9C36DD5514944A8F00D3035F /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C36DD5314944A8F00D3035F /* ViewController.m */; }; 9C36DD5714944F1600D3035F /* milky_way.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 9C36DD5614944F1600D3035F /* milky_way.jpg */; }; + B09F23FA199EA6C100A10972 /* SPUserResizableView.m in Sources */ = {isa = PBXBuildFile; fileRef = B09F23F9199EA6C100A10972 /* SPUserResizableView.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -29,11 +29,11 @@ 9C36DD471494491B00D3035F /* SPUserResizableView-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SPUserResizableView-Prefix.pch"; sourceTree = ""; }; 9C36DD481494491B00D3035F /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 9C36DD491494491B00D3035F /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - 9C36DD5014944A8F00D3035F /* SPUserResizableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPUserResizableView.h; sourceTree = ""; }; - 9C36DD5114944A8F00D3035F /* SPUserResizableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPUserResizableView.m; sourceTree = ""; }; 9C36DD5214944A8F00D3035F /* ViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 9C36DD5314944A8F00D3035F /* ViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 9C36DD5614944F1600D3035F /* milky_way.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = milky_way.jpg; sourceTree = ""; }; + B09F23F8199EA6C100A10972 /* SPUserResizableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPUserResizableView.h; sourceTree = ""; }; + B09F23F9199EA6C100A10972 /* SPUserResizableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPUserResizableView.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -80,8 +80,8 @@ 9C36DD3F1494491A00D3035F /* SPUserResizableView */ = { isa = PBXGroup; children = ( - 9C36DD5014944A8F00D3035F /* SPUserResizableView.h */, - 9C36DD5114944A8F00D3035F /* SPUserResizableView.m */, + B09F23F8199EA6C100A10972 /* SPUserResizableView.h */, + B09F23F9199EA6C100A10972 /* SPUserResizableView.m */, 9C36DD5214944A8F00D3035F /* ViewController.h */, 9C36DD5314944A8F00D3035F /* ViewController.m */, 9C36DD481494491B00D3035F /* AppDelegate.h */, @@ -168,7 +168,7 @@ files = ( 9C36DD461494491B00D3035F /* main.m in Sources */, 9C36DD4A1494491B00D3035F /* AppDelegate.m in Sources */, - 9C36DD5414944A8F00D3035F /* SPUserResizableView.m in Sources */, + B09F23FA199EA6C100A10972 /* SPUserResizableView.m in Sources */, 9C36DD5514944A8F00D3035F /* ViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -192,8 +192,10 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + CLANG_ENABLE_OBJC_WEAK = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; + ENABLE_BITCODE = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; @@ -216,8 +218,10 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + CLANG_ENABLE_OBJC_WEAK = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; + ENABLE_BITCODE = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; @@ -237,6 +241,7 @@ GCC_PREFIX_HEADER = "SPUserResizableView/SPUserResizableView-Prefix.pch"; INFOPLIST_FILE = "SPUserResizableView/SPUserResizableView-Info.plist"; PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; WRAPPER_EXTENSION = app; }; name = Debug; @@ -248,6 +253,7 @@ GCC_PREFIX_HEADER = "SPUserResizableView/SPUserResizableView-Prefix.pch"; INFOPLIST_FILE = "SPUserResizableView/SPUserResizableView-Info.plist"; PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; WRAPPER_EXTENSION = app; }; name = Release; @@ -271,6 +277,7 @@ 9C36DD4F1494491B00D3035F /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; diff --git a/SPUserResizableView.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/SPUserResizableView.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..bb8938a --- /dev/null +++ b/SPUserResizableView.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/SPUserResizableView.xcodeproj/project.xcworkspace/xcshareddata/SPUserResizableView.xccheckout b/SPUserResizableView.xcodeproj/project.xcworkspace/xcshareddata/SPUserResizableView.xccheckout new file mode 100644 index 0000000..612dd6c --- /dev/null +++ b/SPUserResizableView.xcodeproj/project.xcworkspace/xcshareddata/SPUserResizableView.xccheckout @@ -0,0 +1,41 @@ + + + + + IDESourceControlProjectFavoriteDictionaryKey + + IDESourceControlProjectIdentifier + FEFFC6DF-8462-42E6-AAA1-3BD8896B7261 + IDESourceControlProjectName + SPUserResizableView + IDESourceControlProjectOriginsDictionary + + 4519E06DE028306E8037FDB194956AEBC0491ED8 + https://github.com/pionl/SPUserResizableView.git + + IDESourceControlProjectPath + SPUserResizableView.xcodeproj + IDESourceControlProjectRelativeInstallPathDictionary + + 4519E06DE028306E8037FDB194956AEBC0491ED8 + ../.. + + IDESourceControlProjectURL + https://github.com/pionl/SPUserResizableView.git + IDESourceControlProjectVersion + 111 + IDESourceControlProjectWCCIdentifier + 4519E06DE028306E8037FDB194956AEBC0491ED8 + IDESourceControlProjectWCConfigurations + + + IDESourceControlRepositoryExtensionIdentifierKey + public.vcs.git + IDESourceControlWCCIdentifierKey + 4519E06DE028306E8037FDB194956AEBC0491ED8 + IDESourceControlWCCName + SPUserResizableView + + + + diff --git a/SPUserResizableView.xcodeproj/project.xcworkspace/xcuserdata/pion.xcuserdatad/UserInterfaceState.xcuserstate b/SPUserResizableView.xcodeproj/project.xcworkspace/xcuserdata/pion.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..f74c250 Binary files /dev/null and b/SPUserResizableView.xcodeproj/project.xcworkspace/xcuserdata/pion.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/SPUserResizableView/AppDelegate.m b/SPUserResizableView/AppDelegate.m index 38162f2..a27d3b0 100644 --- a/SPUserResizableView/AppDelegate.m +++ b/SPUserResizableView/AppDelegate.m @@ -13,17 +13,12 @@ @implementation AppDelegate @synthesize window, viewController; - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease]; + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.viewController = [[ViewController alloc] initWithNibName:nil bundle:nil]; self.window.rootViewController = self.viewController; [self.window makeKeyAndVisible]; return YES; } -- (void)dealloc { - [viewController release]; - [window release]; - [super dealloc]; -} @end diff --git a/SPUserResizableView/SPUserResizableView.h b/SPUserResizableView/SPUserResizableView.h index 9e4ca94..c112e8a 100644 --- a/SPUserResizableView/SPUserResizableView.h +++ b/SPUserResizableView/SPUserResizableView.h @@ -9,6 +9,15 @@ #import +/* Let's inset everything that's drawn (the handles and the content view) + so that users can trigger a resize from a few pixels outside of + what they actually see as the bounding box. */ +#define kSPUserResizableViewGlobalInset 5.0 + +#define kSPUserResizableViewDefaultMinWidth 48.0 +#define kSPUserResizableViewDefaultMinHeight 48.0 +#define kSPUserResizableViewInteractiveBorderSize 10.0 + typedef struct SPUserResizableViewAnchorPoint { CGFloat adjustsX; CGFloat adjustsY; @@ -21,7 +30,7 @@ typedef struct SPUserResizableViewAnchorPoint { @interface SPUserResizableView : UIView { SPGripViewBorderView *borderView; - UIView *contentView; + UIView *__weak contentView; CGPoint touchStart; CGFloat minWidth; CGFloat minHeight; @@ -29,34 +38,108 @@ typedef struct SPUserResizableViewAnchorPoint { // Used to determine which components of the bounds we'll be modifying, based upon where the user's touch started. SPUserResizableViewAnchorPoint anchorPoint; - id delegate; + id __weak delegate; + + /** + * This will ensure that when the resizing is done, it will restore + * anchor point. + */ + CGPoint m_originalAnchorPoint; + + BOOL didMakeChange; } -@property (nonatomic, assign) id delegate; +@property (nonatomic, weak) id delegate; -// Will be retained as a subview. -@property (nonatomic, assign) UIView *contentView; +/** + * Will be retained as a subview. + */ +@property (nonatomic, weak) UIView *contentView; -// Default is 48.0 for each. +#pragma mark - Setup properties +/** + * Minimum width to let user resize + * @default Default is 48.0 for each. + */ @property (nonatomic) CGFloat minWidth; +/** + * Minimum height that will let user to resize + */ @property (nonatomic) CGFloat minHeight; -// Defaults to YES. Disables the user from dragging the view outside the parent view's bounds. +/** + * Disables resize of the view + * @default NO + */ +@property (nonatomic) BOOL disable; + +/** + * Disables resize of the view if user use more than 1 finger. + * @default NO + */ +@property (nonatomic) BOOL disableOnMultiTouch; +/** + * Defaults to YES. Disables the user from dragging the view outside the parent view's bounds. + */ @property (nonatomic) BOOL preventsPositionOutsideSuperview; +/** + * Defines if pan is disabled. + * @default NO + */ +@property (nonatomic) BOOL disablePan; + +/** + * Defines the insent of the content. + * Larger == better detection + */ +@property (nonatomic) float resizableInset; +/** + * Interactive border size + */ +@property (nonatomic) float interactiveBorderSize; + +/** + * Hide editing handles + */ - (void)hideEditingHandles; +/** + * Shows editing handles + */ - (void)showEditingHandles; +/** + * Is currently resizing? + * + * @return BOOL + */ +- (BOOL)isResizing; + @end @protocol SPUserResizableViewDelegate @optional -// Called when the resizable view receives touchesBegan: and activates the editing handles. +/** + * Called when the resizable view receives touchesBegan: and activates the editing handles. + * + * @param userResizableView + */ - (void)userResizableViewDidBeginEditing:(SPUserResizableView *)userResizableView; -// Called when the resizable view receives touchesEnded: or touchesCancelled: +/** + * Called when the resizable view receives touchesEnded: or touchesCancelled: + * + * @param userResizableView + */ - (void)userResizableViewDidEndEditing:(SPUserResizableView *)userResizableView; +/** + * Called when new frame was set. + * + * @param userResizableView + */ +- (void)userResizableViewNewRealFrame:(SPUserResizableView *)userResizableView; + @end diff --git a/SPUserResizableView/SPUserResizableView.m b/SPUserResizableView/SPUserResizableView.m index c2681a7..cbcab41 100644 --- a/SPUserResizableView/SPUserResizableView.m +++ b/SPUserResizableView/SPUserResizableView.m @@ -6,15 +6,7 @@ // #import "SPUserResizableView.h" - -/* Let's inset everything that's drawn (the handles and the content view) - so that users can trigger a resize from a few pixels outside of - what they actually see as the bounding box. */ -#define kSPUserResizableViewGlobalInset 5.0 - -#define kSPUserResizableViewDefaultMinWidth 48.0 -#define kSPUserResizableViewDefaultMinHeight 48.0 -#define kSPUserResizableViewInteractiveBorderSize 10.0 +#define RADIANS_TO_DEGREES(radians) ((radians) * (180.0 / M_PI)) static SPUserResizableViewAnchorPoint SPUserResizableViewNoResizeAnchorPoint = { 0.0, 0.0, 0.0, 0.0 }; static SPUserResizableViewAnchorPoint SPUserResizableViewUpperLeftAnchorPoint = { 1.0, 1.0, -1.0, 1.0 }; @@ -27,6 +19,10 @@ static SPUserResizableViewAnchorPoint SPUserResizableViewLowerMiddleAnchorPoint = { 0.0, 0.0, 1.0, 0.0 }; @interface SPGripViewBorderView : UIView + +@property (nonatomic) float resizableInset; +@property (nonatomic) float interactiveBorderSize; + @end @implementation SPGripViewBorderView @@ -46,22 +42,22 @@ - (void)drawRect:(CGRect)rect { // (1) Draw the bounding box. CGContextSetLineWidth(context, 1.0); CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor); - CGContextAddRect(context, CGRectInset(self.bounds, kSPUserResizableViewInteractiveBorderSize/2, kSPUserResizableViewInteractiveBorderSize/2)); + CGContextAddRect(context, CGRectInset(self.bounds, [self interactiveBorderSize]/2, [self interactiveBorderSize]/2)); CGContextStrokePath(context); // (2) Calculate the bounding boxes for each of the anchor points. - CGRect upperLeft = CGRectMake(0.0, 0.0, kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize); - CGRect upperRight = CGRectMake(self.bounds.size.width - kSPUserResizableViewInteractiveBorderSize, 0.0, kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize); - CGRect lowerRight = CGRectMake(self.bounds.size.width - kSPUserResizableViewInteractiveBorderSize, self.bounds.size.height - kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize); - CGRect lowerLeft = CGRectMake(0.0, self.bounds.size.height - kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize); - CGRect upperMiddle = CGRectMake((self.bounds.size.width - kSPUserResizableViewInteractiveBorderSize)/2, 0.0, kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize); - CGRect lowerMiddle = CGRectMake((self.bounds.size.width - kSPUserResizableViewInteractiveBorderSize)/2, self.bounds.size.height - kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize); - CGRect middleLeft = CGRectMake(0.0, (self.bounds.size.height - kSPUserResizableViewInteractiveBorderSize)/2, kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize); - CGRect middleRight = CGRectMake(self.bounds.size.width - kSPUserResizableViewInteractiveBorderSize, (self.bounds.size.height - kSPUserResizableViewInteractiveBorderSize)/2, kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize); + CGRect upperLeft = CGRectMake(0.0, 0.0, [self interactiveBorderSize], [self interactiveBorderSize]); + CGRect upperRight = CGRectMake(self.bounds.size.width - [self interactiveBorderSize], 0.0, [self interactiveBorderSize], [self interactiveBorderSize]); + CGRect lowerRight = CGRectMake(self.bounds.size.width - [self interactiveBorderSize], self.bounds.size.height - [self interactiveBorderSize], [self interactiveBorderSize], [self interactiveBorderSize]); + CGRect lowerLeft = CGRectMake(0.0, self.bounds.size.height - [self interactiveBorderSize], [self interactiveBorderSize], [self interactiveBorderSize]); + CGRect upperMiddle = CGRectMake((self.bounds.size.width - [self interactiveBorderSize])/2, 0.0, [self interactiveBorderSize], [self interactiveBorderSize]); + CGRect lowerMiddle = CGRectMake((self.bounds.size.width - [self interactiveBorderSize])/2, self.bounds.size.height - [self interactiveBorderSize], [self interactiveBorderSize], [self interactiveBorderSize]); + CGRect middleLeft = CGRectMake(0.0, (self.bounds.size.height - [self interactiveBorderSize])/2, [self interactiveBorderSize], [self interactiveBorderSize]); + CGRect middleRight = CGRectMake(self.bounds.size.width - [self interactiveBorderSize], (self.bounds.size.height - [self interactiveBorderSize])/2, [self interactiveBorderSize], [self interactiveBorderSize]); // (3) Create the gradient to paint the anchor points. - CGFloat colors [] = { - 0.4, 0.8, 1.0, 1.0, + CGFloat colors [] = { + 0.4, 0.8, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0 }; CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceRGB(); @@ -92,17 +88,67 @@ - (void)drawRect:(CGRect)rect { @end +@interface SPUserResizableView () + +- (void)translateUsingTouchLocation:(CGPoint)touchPoint; + +/** + * Used for moving anchorPoint without loosing current position. Works with transform + * @author http://stackoverflow.com/a/5666430/740949 + * + * @param anchor CGPoint for new anchor + * @param view + */ +-(void)setAnchorPoint:(CGPoint)anchor; + +/** + * Determines if we should not resize the by current settings. + * + * @param touches + * + * @return BOOL + */ +- (BOOL)isDisabledForTouches:(NSSet*)touches; + +/** + * Checks if user did change the view + * + * @return BOOL + */ +- (BOOL)willResize:(CGPoint)point; + +/** + * Triggered when touches finished or canceled + * + * @param touches + */ +- (void)touchesFinished:(NSSet *)touches; + +@end + @implementation SPUserResizableView @synthesize contentView, minWidth, minHeight, preventsPositionOutsideSuperview, delegate; + - (void)setupDefaultAttributes { - borderView = [[SPGripViewBorderView alloc] initWithFrame:CGRectInset(self.bounds, kSPUserResizableViewGlobalInset, kSPUserResizableViewGlobalInset)]; + + // craete border view + borderView = [[SPGripViewBorderView alloc] initWithFrame:CGRectInset(self.bounds, [self resizableInset], [self resizableInset])]; [borderView setHidden:YES]; + [self addSubview:borderView]; + + // setup self.minWidth = kSPUserResizableViewDefaultMinWidth; self.minHeight = kSPUserResizableViewDefaultMinHeight; self.preventsPositionOutsideSuperview = YES; + + _disable = NO; + + [self setResizableInset:kSPUserResizableViewGlobalInset]; + [self setInteractiveBorderSize:kSPUserResizableViewInteractiveBorderSize]; + } - (id)initWithFrame:(CGRect)frame { @@ -119,24 +165,264 @@ - (id)initWithCoder:(NSCoder *)aDecoder { return self; } +// we need to update also border view +- (void)setResizableInset:(float)resizableInset { + _resizableInset = resizableInset; + [borderView setResizableInset:resizableInset]; + + [self setFrame:[self frame]]; +} + +- (void)setInteractiveBorderSize:(float)interactiveBorderSize { + _interactiveBorderSize = interactiveBorderSize; + [borderView setInteractiveBorderSize:interactiveBorderSize]; + + [self setFrame:[self frame]]; +} + - (void)setContentView:(UIView *)newContentView { [contentView removeFromSuperview]; contentView = newContentView; - contentView.frame = CGRectInset(self.bounds, kSPUserResizableViewGlobalInset + kSPUserResizableViewInteractiveBorderSize/2, kSPUserResizableViewGlobalInset + kSPUserResizableViewInteractiveBorderSize/2); + + contentView.frame = CGRectInset(self.bounds, [self resizableInset] + [self interactiveBorderSize]/2, [self resizableInset] + [self interactiveBorderSize]/2); + [self addSubview:contentView]; // Ensure the border view is always on top by removing it and adding it to the end of the subview list. [borderView removeFromSuperview]; [self addSubview:borderView]; + [self setFrame:[self frame]]; } - (void)setFrame:(CGRect)newFrame { [super setFrame:newFrame]; - contentView.frame = CGRectInset(self.bounds, kSPUserResizableViewGlobalInset + kSPUserResizableViewInteractiveBorderSize/2, kSPUserResizableViewGlobalInset + kSPUserResizableViewInteractiveBorderSize/2); - borderView.frame = CGRectInset(self.bounds, kSPUserResizableViewGlobalInset, kSPUserResizableViewGlobalInset); - [borderView setNeedsDisplay]; + + if (contentView) { + contentView.frame = CGRectInset(self.bounds, [self resizableInset] + [self interactiveBorderSize]/2, [self resizableInset] + [self interactiveBorderSize]/2); + borderView.frame = CGRectInset(self.bounds, [self resizableInset], [self resizableInset]); + [borderView setNeedsDisplay]; + } +} + +- (BOOL)isResizing { + return (anchorPoint.adjustsH || anchorPoint.adjustsW || anchorPoint.adjustsX || anchorPoint.adjustsY); +} + +- (BOOL)isDisabledForTouches:(NSSet*)touches { + return ([self disable] || ([self disableOnMultiTouch] && [touches count] > 1)); +} + +- (BOOL)willResize:(CGPoint)point { + // dermine if we will make resize + return [self isResizing] && (point.x != touchStart.x && point.y != touchStart.y); +} + +- (void)showEditingHandles { + [borderView setHidden:NO]; +} + +- (void)hideEditingHandles { + [borderView setHidden:YES]; +} + +#pragma mark - Resize + +- (void)resizeUsingTouchLocation:(CGPoint)touchPoint { + if ([self disable]) { + return; + } + + //NSLog(@"Touch point %@",NSStringFromCGPoint(touchPoint)); + // save current rotation and scales + CGFloat scaleX = [[self valueForKeyPath:@"layer.transform.scale.x"] floatValue]; + CGFloat scaleY = [[self valueForKeyPath:@"layer.transform.scale.y"] floatValue]; + CGFloat rotation = [[self valueForKeyPath:@"layer.transform.rotation"] floatValue]; + + // update current anchor point to update frane with transform + + //NSLog(@"H %f, W %f, X %f, Y %f", anchorPoint.adjustsH, anchorPoint.adjustsW, anchorPoint.adjustsX, anchorPoint.adjustsY); + + + //NSLog(@"Rotation %f",RADIANS_TO_DEGREES(rotation)); + + + CGPoint point; + + if (anchorPoint.adjustsY != 0) { + if (anchorPoint.adjustsW != 0 && anchorPoint.adjustsX == 0) { + point = CGPointMake(0, 1); + } else { + point = CGPointMake(1, 1); + } + } else if (anchorPoint.adjustsX != 0) { + point = CGPointMake(1, 0); + } else { + point = CGPointMake(0, 0); + } + + + [self setAnchorPoint:point]; + + // restore to normal cords + [self setTransform:CGAffineTransformIdentity]; + + // (1) Update the touch point if we're outside the superview. + + if (self.preventsPositionOutsideSuperview) { + CGFloat border = [self resizableInset] + [self interactiveBorderSize]/2; + if (touchPoint.x < border) { + touchPoint.x = border; + } + if (touchPoint.x > self.superview.bounds.size.width - border) { + touchPoint.x = self.superview.bounds.size.width - border; + } + if (touchPoint.y < border) { + touchPoint.y = border; + } + if (touchPoint.y > self.superview.bounds.size.height - border) { + touchPoint.y = self.superview.bounds.size.height - border; + } + } + + // (2) Calculate the deltas using the current anchor point. + + CGPoint start = touchStart; + CGPoint end = touchPoint; + + float rotationDeg = RADIANS_TO_DEGREES(rotation); + + if (rotationDeg > 45.0 && rotationDeg < 135.0) { + + start.x = touchStart.y; + start.y = touchPoint.x; + + end.x = touchPoint.y; + end.y = touchStart.x; + } else if (-45.0 > rotationDeg && rotationDeg > -135.0) { + start.x = touchPoint.y; + start.y = touchStart.x; + + end.x = touchStart.y; + end.y = touchPoint.x; + //} else if (-135.0 > rotationDeg) { + + } else if (rotationDeg > 135.0 || -135.0 > rotationDeg) { + start = touchPoint; + end = touchStart; + } + + + + CGFloat deltaW = anchorPoint.adjustsW * (start.x - end.x) / scaleX; + CGFloat deltaX = anchorPoint.adjustsX * (-1.0 * deltaW); + CGFloat deltaH = anchorPoint.adjustsH * (end.y - start.y) / scaleY; + CGFloat deltaY = anchorPoint.adjustsY * (-1.0 * deltaH); + + // (3) Calculate the new frame. + CGFloat newX = self.frame.origin.x + deltaX; + CGFloat newY = self.frame.origin.y + deltaY; + CGFloat newWidth = self.bounds.size.width + deltaW; + CGFloat newHeight = self.bounds.size.height + deltaH; + + // (4) If the new frame is too small, cancel the changes. + if (newWidth < self.minWidth) { + newX -= anchorPoint.adjustsX * (self.minWidth - newWidth); + newWidth = self.minWidth; + } + if (newHeight < self.minHeight) { + newY -= anchorPoint.adjustsY * (self.minHeight - newHeight); + newHeight = self.minHeight; + } + + // (5) Ensure the resize won't cause the view to move offscreen. + if (self.preventsPositionOutsideSuperview) { + /* + if (newX < self.superview.bounds.origin.x) { + // Calculate how much to grow the width by such that the new X coordintae will align with the superview. + deltaW = self.frame.origin.x - self.superview.bounds.origin.x; + newWidth = self.frame.size.width + deltaW; + newX = self.superview.bounds.origin.x; + } + if (newX + newWidth > self.superview.bounds.origin.x + self.superview.bounds.size.width) { + newWidth = self.superview.bounds.size.width - newX; + } + if (newY < self.superview.bounds.origin.y) { + // Calculate how much to grow the height by such that the new Y coordintae will align with the superview. + deltaH = self.bounds.origin.y - self.superview.bounds.origin.y; + newHeight = self.frame.size.height + deltaH; + newY = self.superview.bounds.origin.y; + } + if (newY + newHeight > self.superview.bounds.origin.y + self.superview.bounds.size.height) { + newHeight = self.superview.bounds.size.height - newY; + }*/ + } + + // update the frame + self.frame = CGRectMake(newX, newY, newWidth, newHeight); + + if ([self delegate] && [[self delegate] respondsToSelector:@selector(userResizableViewNewRealFrame:)]) { + [[self delegate] userResizableViewNewRealFrame:self]; + } + // resotre the transform + CGAffineTransform transform = CGAffineTransformMakeRotation(rotation); + + [self setTransform:CGAffineTransformScale(transform, scaleX, scaleY)]; + + + touchStart = touchPoint; } +#pragma mark - Helper functions + +- (void)translateUsingTouchLocation:(CGPoint)touchPoint { + //[self setAnchorPoint:CGPointMake(0.5, 0.5)]; + + CGPoint newCenter = CGPointMake(self.center.x + touchPoint.x - touchStart.x, self.center.y + touchPoint.y - touchStart.y); + + if (self.preventsPositionOutsideSuperview) {/* + // Ensure the translation won't cause the view to move offscreen. + + CGFloat midPointX = CGRectGetMidX(self.bounds); + if (newCenter.x > self.superview.bounds.size.width - midPointX) { + newCenter.x = self.superview.bounds.size.width - midPointX; + } + if (newCenter.x < midPointX) { + newCenter.x = midPointX; + } + CGFloat midPointY = CGRectGetMidY(self.bounds); + if (newCenter.y > self.superview.bounds.size.height - midPointY) { + newCenter.y = self.superview.bounds.size.height - midPointY; + } + if (newCenter.y < midPointY) { + newCenter.y = midPointY; + }*/ + } + self.center = newCenter; +} + +- (void)setAnchorPoint:(CGPoint)anchor { + CGPoint newPoint = CGPointMake(self.bounds.size.width * anchor.x, + self.bounds.size.height * anchor.y); + CGPoint oldPoint = CGPointMake(self.bounds.size.width * self.layer.anchorPoint.x, + self.bounds.size.height * self.layer.anchorPoint.y); + + newPoint = CGPointApplyAffineTransform(newPoint, self.transform); + oldPoint = CGPointApplyAffineTransform(oldPoint, self.transform); + + CGPoint position = self.layer.position; + + position.x -= oldPoint.x; + position.x += newPoint.x; + + position.y -= oldPoint.y; + position.y += newPoint.y; + + self.layer.position = position; + self.layer.anchorPoint = anchor; +} + + + static CGFloat SPDistanceBetweenTwoPoints(CGPoint point1, CGPoint point2) { CGFloat dx = point2.x - point1.x; CGFloat dy = point2.y - point1.y; @@ -149,6 +435,8 @@ static CGFloat SPDistanceBetweenTwoPoints(CGPoint point1, CGPoint point2) { } CGPointSPUserResizableViewAnchorPointPair; - (SPUserResizableViewAnchorPoint)anchorPointForTouchLocation:(CGPoint)touchPoint { + + // (1) Calculate the positions of each of the anchor points. CGPointSPUserResizableViewAnchorPointPair upperLeft = { CGPointMake(0.0, 0.0), SPUserResizableViewUpperLeftAnchorPoint }; CGPointSPUserResizableViewAnchorPointPair upperMiddle = { CGPointMake(self.bounds.size.width/2, 0.0), SPUserResizableViewUpperMiddleAnchorPoint }; @@ -165,19 +453,32 @@ - (SPUserResizableViewAnchorPoint)anchorPointForTouchLocation:(CGPoint)touchPoin CGFloat smallestDistance = MAXFLOAT; CGPointSPUserResizableViewAnchorPointPair closestPoint = centerPoint; for (NSInteger i = 0; i < 9; i++) { CGFloat distance = SPDistanceBetweenTwoPoints(touchPoint, allPoints[i].point); - if (distance < smallestDistance) { + if (distance < smallestDistance) { closestPoint = allPoints[i]; smallestDistance = distance; } } - return closestPoint.anchorPoint; + + + // make dragable only small portion of border. + float check = ([self resizableInset]+5) * 2; + + if (touchPoint.x < check+[self resizableInset] || touchPoint.x >= (self.bounds.size.width-check) || touchPoint.y < check+[self resizableInset] || touchPoint.y >= (self.bounds.size.height-check)) { + return closestPoint.anchorPoint; + } else { + return (SPUserResizableViewAnchorPoint){0,0,0,0}; + } } -- (BOOL)isResizing { - return (anchorPoint.adjustsH || anchorPoint.adjustsW || anchorPoint.adjustsX || anchorPoint.adjustsY); -} +#pragma mark - Touches - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { + if ([self isDisabledForTouches:touches]) { + return; + } + + //m_originalAnchorPoint = [[self layer] anchorPoint]; + // Notify the delegate we've begun our editing session. if (self.delegate && [self.delegate respondsToSelector:@selector(userResizableViewDidBeginEditing:)]) { [self.delegate userResizableViewDidBeginEditing:self]; @@ -185,6 +486,7 @@ - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [borderView setHidden:NO]; UITouch *touch = [touches anyObject]; + anchorPoint = [self anchorPointForTouchLocation:[touch locationInView:self]]; // When resizing, all calculations are done in the superview's coordinate space. @@ -196,127 +498,45 @@ - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { - // Notify the delegate we've ended our editing session. - if (self.delegate && [self.delegate respondsToSelector:@selector(userResizableViewDidEndEditing:)]) { - [self.delegate userResizableViewDidEndEditing:self]; - } + [self touchesFinished:touches]; } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { - // Notify the delegate we've ended our editing session. - if (self.delegate && [self.delegate respondsToSelector:@selector(userResizableViewDidEndEditing:)]) { - [self.delegate userResizableViewDidEndEditing:self]; - } + [self touchesFinished:touches]; } -- (void)showEditingHandles { - [borderView setHidden:NO]; -} - -- (void)hideEditingHandles { - [borderView setHidden:YES]; -} - -- (void)resizeUsingTouchLocation:(CGPoint)touchPoint { - // (1) Update the touch point if we're outside the superview. - if (self.preventsPositionOutsideSuperview) { - CGFloat border = kSPUserResizableViewGlobalInset + kSPUserResizableViewInteractiveBorderSize/2; - if (touchPoint.x < border) { - touchPoint.x = border; - } - if (touchPoint.x > self.superview.bounds.size.width - border) { - touchPoint.x = self.superview.bounds.size.width - border; - } - if (touchPoint.y < border) { - touchPoint.y = border; - } - if (touchPoint.y > self.superview.bounds.size.height - border) { - touchPoint.y = self.superview.bounds.size.height - border; - } - } - - // (2) Calculate the deltas using the current anchor point. - CGFloat deltaW = anchorPoint.adjustsW * (touchStart.x - touchPoint.x); - CGFloat deltaX = anchorPoint.adjustsX * (-1.0 * deltaW); - CGFloat deltaH = anchorPoint.adjustsH * (touchPoint.y - touchStart.y); - CGFloat deltaY = anchorPoint.adjustsY * (-1.0 * deltaH); - - // (3) Calculate the new frame. - CGFloat newX = self.frame.origin.x + deltaX; - CGFloat newY = self.frame.origin.y + deltaY; - CGFloat newWidth = self.frame.size.width + deltaW; - CGFloat newHeight = self.frame.size.height + deltaH; - - // (4) If the new frame is too small, cancel the changes. - if (newWidth < self.minWidth) { - newWidth = self.frame.size.width; - newX = self.frame.origin.x; - } - if (newHeight < self.minHeight) { - newHeight = self.frame.size.height; - newY = self.frame.origin.y; +- (void)touchesFinished:(NSSet *)touches { + if ((didMakeChange || ![self disable]) && self.delegate && [self.delegate respondsToSelector:@selector(userResizableViewDidEndEditing:)]) { + [self.delegate userResizableViewDidEndEditing:self]; } - // (5) Ensure the resize won't cause the view to move offscreen. - if (self.preventsPositionOutsideSuperview) { - if (newX < self.superview.bounds.origin.x) { - // Calculate how much to grow the width by such that the new X coordintae will align with the superview. - deltaW = self.frame.origin.x - self.superview.bounds.origin.x; - newWidth = self.frame.size.width + deltaW; - newX = self.superview.bounds.origin.x; - } - if (newX + newWidth > self.superview.bounds.origin.x + self.superview.bounds.size.width) { - newWidth = self.superview.bounds.size.width - newX; - } - if (newY < self.superview.bounds.origin.y) { - // Calculate how much to grow the height by such that the new Y coordintae will align with the superview. - deltaH = self.frame.origin.y - self.superview.bounds.origin.y; - newHeight = self.frame.size.height + deltaH; - newY = self.superview.bounds.origin.y; - } - if (newY + newHeight > self.superview.bounds.origin.y + self.superview.bounds.size.height) { - newHeight = self.superview.bounds.size.height - newY; - } - } + didMakeChange = NO; - self.frame = CGRectMake(newX, newY, newWidth, newHeight); - touchStart = touchPoint; -} - -- (void)translateUsingTouchLocation:(CGPoint)touchPoint { - CGPoint newCenter = CGPointMake(self.center.x + touchPoint.x - touchStart.x, self.center.y + touchPoint.y - touchStart.y); - if (self.preventsPositionOutsideSuperview) { - // Ensure the translation won't cause the view to move offscreen. - CGFloat midPointX = CGRectGetMidX(self.bounds); - if (newCenter.x > self.superview.bounds.size.width - midPointX) { - newCenter.x = self.superview.bounds.size.width - midPointX; - } - if (newCenter.x < midPointX) { - newCenter.x = midPointX; - } - CGFloat midPointY = CGRectGetMidY(self.bounds); - if (newCenter.y > self.superview.bounds.size.height - midPointY) { - newCenter.y = self.superview.bounds.size.height - midPointY; - } - if (newCenter.y < midPointY) { - newCenter.y = midPointY; - } + if ([self isDisabledForTouches:touches]) { + return; } - self.center = newCenter; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { - if ([self isResizing]) { - [self resizeUsingTouchLocation:[[touches anyObject] locationInView:self.superview]]; - } else { - [self translateUsingTouchLocation:[[touches anyObject] locationInView:self]]; + // is disabled or there are more touches + if (![self isDisabledForTouches:touches]) { + CGPoint point = [[touches anyObject] locationInView:self.superview]; + + if ([self isResizing] && [touches count] == 1) { + if ([self willResize:point]) { + didMakeChange = YES; + [self resizeUsingTouchLocation:point]; + } + } else if (![self disablePan]){ + didMakeChange = YES; + + [self translateUsingTouchLocation:[[touches anyObject] locationInView:self]]; + } } } - (void)dealloc { [contentView removeFromSuperview]; - [borderView release]; - [super dealloc]; } @end diff --git a/SPUserResizableView/ViewController.h b/SPUserResizableView/ViewController.h index 7ba5829..5d03df1 100644 --- a/SPUserResizableView/ViewController.h +++ b/SPUserResizableView/ViewController.h @@ -11,6 +11,19 @@ @interface ViewController : UIViewController { SPUserResizableView *currentlyEditingView; SPUserResizableView *lastEditedView; + + /** + * Pinch lastScale + */ + CGFloat lastScale; + + /** + * Rotate lastRotation + */ + CGFloat lastRotation; } +- (void)handlePinchGesture:(UIPinchGestureRecognizer *)gestureRecognizer; +- (void)handleRotateGesture:(UIRotationGestureRecognizer*)sender; + @end diff --git a/SPUserResizableView/ViewController.m b/SPUserResizableView/ViewController.m index 2bbe3c2..0ee32c0 100644 --- a/SPUserResizableView/ViewController.m +++ b/SPUserResizableView/ViewController.m @@ -25,21 +25,40 @@ - (void)viewDidLoad { currentlyEditingView = userResizableView; lastEditedView = userResizableView; [self.view addSubview:userResizableView]; - [contentView release]; [userResizableView release]; // (2) Create a second resizable view with a UIImageView as the content. - CGRect imageFrame = CGRectMake(50, 200, 200, 200); + CGRect imageFrame = CGRectMake(200, 200, 200, 200); SPUserResizableView *imageResizableView = [[SPUserResizableView alloc] initWithFrame:imageFrame]; + UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"milky_way.jpg"]]; + + [imageResizableView setInteractiveBorderSize:12.0]; + [imageResizableView setMinWidth:140.0]; + [imageResizableView setMinHeight:120.0]; + [imageResizableView setResizableInset:30]; + imageResizableView.contentView = imageView; imageResizableView.delegate = self; + imageResizableView.disablePan = NO; + + [imageResizableView setTransform:CGAffineTransformMakeRotation(0.1)]; + [self.view addSubview:imageResizableView]; - [imageView release]; [imageResizableView release]; UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(hideEditingHandles)]; [gestureRecognizer setDelegate:self]; [self.view addGestureRecognizer:gestureRecognizer]; - [gestureRecognizer release]; + + UIPinchGestureRecognizer *pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinchGesture:)]; + [pinchRecognizer setDelegate:self]; + [userResizableView addGestureRecognizer:pinchRecognizer]; + [imageResizableView addGestureRecognizer:pinchRecognizer]; + + UIRotationGestureRecognizer *rotationRecognizer = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(handleRotateGesture:)]; + [rotationRecognizer setDelegate:self]; + + [userResizableView addGestureRecognizer:rotationRecognizer]; + [imageResizableView addGestureRecognizer:rotationRecognizer]; } - (void)userResizableViewDidBeginEditing:(SPUserResizableView *)userResizableView { @@ -52,16 +71,64 @@ - (void)userResizableViewDidEndEditing:(SPUserResizableView *)userResizableView } - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { + if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) { if ([currentlyEditingView hitTest:[touch locationInView:currentlyEditingView] withEvent:nil]) { return NO; } + } + return YES; +} +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { return YES; } - - (void)hideEditingHandles { // We only want the gesture recognizer to end the editing session on the last // edited view. We wouldn't want to dismiss an editing session in progress. [lastEditedView hideEditingHandles]; } +- (void)handlePinchGesture:(UIPinchGestureRecognizer *)gestureRecognizer { + + if([gestureRecognizer state] == UIGestureRecognizerStateBegan) { + // Reset the last scale, necessary if there are multiple objects with different scales + lastScale = [gestureRecognizer scale]; + } + + if ([gestureRecognizer state] == UIGestureRecognizerStateBegan || + [gestureRecognizer state] == UIGestureRecognizerStateChanged) { + + CGFloat currentScale = [[[gestureRecognizer view].layer valueForKeyPath:@"transform.scale"] floatValue]; + + // Constants to adjust the max/min values of zoom + const CGFloat kMaxScale = 2.0; + const CGFloat kMinScale = 1.0; + + CGFloat newScale = 1 - (lastScale - [gestureRecognizer scale]); + newScale = MIN(newScale, kMaxScale / currentScale); + newScale = MAX(newScale, kMinScale / currentScale); + CGAffineTransform transform = CGAffineTransformScale([[gestureRecognizer view] transform], newScale, newScale); + [gestureRecognizer view].transform = transform; + + lastScale = [gestureRecognizer scale]; // Store the previous scale factor for the next pinch gesture call + } +} + +-(void)handleRotateGesture:(UIRotationGestureRecognizer*)sender { + + if([(UIRotationGestureRecognizer*)sender state] == UIGestureRecognizerStateEnded) { + + lastRotation = 0.0; + return; + } + + CGFloat rotation = 0.0 - (lastRotation - [sender rotation]); + + CGAffineTransform currentTransform = [sender view] .transform; + CGAffineTransform newTransform = CGAffineTransformRotate(currentTransform,rotation); + + [[sender view] setTransform:newTransform]; + + lastRotation = [(UIRotationGestureRecognizer*)sender rotation]; +} + @end diff --git a/SPUserResizableView/milky_way.jpg b/SPUserResizableView/milky_way.jpg index 83f0a8d..2089c62 100644 Binary files a/SPUserResizableView/milky_way.jpg and b/SPUserResizableView/milky_way.jpg differ