├── SPUserResizableView ├── en.lproj │ └── InfoPlist.strings ├── milky_way.jpg ├── AppDelegate.h ├── SPUserResizableView-Prefix.pch ├── ViewController.h ├── main.m ├── AppDelegate.m ├── SPUserResizableView-Info.plist ├── SPUserResizableView.h ├── ViewController.m └── SPUserResizableView.m ├── LICENSE ├── README.md └── SPUserResizableView.xcodeproj └── project.pbxproj /SPUserResizableView/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /SPUserResizableView/milky_way.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spoletto/SPUserResizableView/HEAD/SPUserResizableView/milky_way.jpg -------------------------------------------------------------------------------- /SPUserResizableView/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // SPUserResizableView 4 | // 5 | // Created by Stephen Poletto on 12/10/11. 6 | // 7 | 8 | #import 9 | 10 | @class ViewController; 11 | 12 | @interface AppDelegate : UIResponder 13 | 14 | @property (strong, nonatomic) UIWindow *window; 15 | @property (strong, nonatomic) ViewController *viewController; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /SPUserResizableView/SPUserResizableView-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'SPUserResizableView' target in the 'SPUserResizableView' 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 | -------------------------------------------------------------------------------- /SPUserResizableView/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // SPUserResizableView 4 | // 5 | // Created by Stephen Poletto on 12/10/11. 6 | // 7 | 8 | #import "SPUserResizableView.h" 9 | #import 10 | 11 | @interface ViewController : UIViewController { 12 | SPUserResizableView *currentlyEditingView; 13 | SPUserResizableView *lastEditedView; 14 | } 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /SPUserResizableView/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // SPUserResizableView 4 | // 5 | // Created by Stephen Poletto on 12/10/11. 6 | // Copyright (c) 2011 Brown University. 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 | -------------------------------------------------------------------------------- /SPUserResizableView/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // SPUserResizableView 4 | // 5 | // Created by Stephen Poletto on 12/10/11. 6 | // 7 | 8 | #import "ViewController.h" 9 | #import "AppDelegate.h" 10 | 11 | @implementation AppDelegate 12 | 13 | @synthesize window, viewController; 14 | 15 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 16 | self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease]; 17 | self.viewController = [[ViewController alloc] initWithNibName:nil bundle:nil]; 18 | self.window.rootViewController = self.viewController; 19 | [self.window makeKeyAndVisible]; 20 | return YES; 21 | } 22 | 23 | - (void)dealloc { 24 | [viewController release]; 25 | [window release]; 26 | [super dealloc]; 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Stephen Poletto 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /SPUserResizableView/SPUserResizableView-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIconFiles 12 | 13 | CFBundleIdentifier 14 | com.spoletto.${PRODUCT_NAME:rfc1034identifier} 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | ${PRODUCT_NAME} 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | 1.0 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | 1.0 27 | LSRequiresIPhoneOS 28 | 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /SPUserResizableView/SPUserResizableView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPUserResizableView.h 3 | // SPUserResizableView 4 | // 5 | // Created by Stephen Poletto on 12/10/11. 6 | // 7 | // SPUserResizableView is a user-resizable, user-repositionable 8 | // UIView subclass. 9 | 10 | #import 11 | 12 | typedef struct SPUserResizableViewAnchorPoint { 13 | CGFloat adjustsX; 14 | CGFloat adjustsY; 15 | CGFloat adjustsH; 16 | CGFloat adjustsW; 17 | } SPUserResizableViewAnchorPoint; 18 | 19 | @protocol SPUserResizableViewDelegate; 20 | @class SPGripViewBorderView; 21 | 22 | @interface SPUserResizableView : UIView { 23 | SPGripViewBorderView *borderView; 24 | UIView *contentView; 25 | CGPoint touchStart; 26 | CGFloat minWidth; 27 | CGFloat minHeight; 28 | 29 | // Used to determine which components of the bounds we'll be modifying, based upon where the user's touch started. 30 | SPUserResizableViewAnchorPoint anchorPoint; 31 | 32 | id delegate; 33 | } 34 | 35 | @property (nonatomic, assign) id delegate; 36 | 37 | // Will be retained as a subview. 38 | @property (nonatomic, assign) UIView *contentView; 39 | 40 | // Default is 48.0 for each. 41 | @property (nonatomic) CGFloat minWidth; 42 | @property (nonatomic) CGFloat minHeight; 43 | 44 | // Defaults to YES. Disables the user from dragging the view outside the parent view's bounds. 45 | @property (nonatomic) BOOL preventsPositionOutsideSuperview; 46 | 47 | - (void)hideEditingHandles; 48 | - (void)showEditingHandles; 49 | 50 | @end 51 | 52 | @protocol SPUserResizableViewDelegate 53 | 54 | @optional 55 | 56 | // Called when the resizable view receives touchesBegan: and activates the editing handles. 57 | - (void)userResizableViewDidBeginEditing:(SPUserResizableView *)userResizableView; 58 | 59 | // Called when the resizable view receives touchesEnded: or touchesCancelled: 60 | - (void)userResizableViewDidEndEditing:(SPUserResizableView *)userResizableView; 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SPUserResizableView 2 | =============== 3 | 4 | SPUserResizableView is a user-resizable, user-repositionable UIView subclass. It is modeled after the resizable image view from the Pages iOS app. Any UIView can be provided as the content view for the SPUserResizableView. As the view is respositioned and resized, setFrame: will be called on the content view accordingly. 5 | 6 | Screenshot 7 | ---- 8 | ![SPUserResizableView](http://i.imgur.com/krBjw.png) 9 | 10 | How To Use It 11 | ------------- 12 | 13 | ### Installation 14 | 15 | Include SPUserResizableView.h and SPUserResizableView.m in your project. 16 | 17 | ### Setting up the SPUserResizableView 18 | 19 | You'll need to #import the SPUserResizableView.h header and construct a new instance of SPUserResizableView. Then, set the contentView on the SPUserResizableView to the view you'd like the user to interact with. 20 | 21 | ``` objective-c 22 | #import "SPUserResizableView.h" 23 | 24 | ... 25 | 26 | - (void)viewDidLoad { 27 | CGRect frame = CGRectMake(50, 50, 200, 150); 28 | SPUserResizableView *userResizableView = [[SPUserResizableView alloc] initWithFrame:frame]; 29 | UIView *contentView = [[UIView alloc] initWithFrame:frame]; 30 | [contentView setBackgroundColor:[UIColor redColor]]; 31 | userResizableView.contentView = contentView; 32 | [self.view addSubview:userResizableView]; 33 | [contentView release]; 34 | [userResizableView release]; 35 | } 36 | ``` 37 | 38 | If you'd like to receive callbacks when the SPUserResizableView receives touchBegan:, touchesEnded: and touchesCancelled: messages, set the delegate on the SPUserResizableView accordingly. 39 | 40 | ``` objective-c 41 | userResizableView.delegate = self; 42 | ``` 43 | 44 | Then implement the following delegate methods. 45 | 46 | ``` objective-c 47 | - (void)userResizableViewDidBeginEditing:(SPUserResizableView *)userResizableView; 48 | - (void)userResizableViewDidEndEditing:(SPUserResizableView *)userResizableView; 49 | ``` 50 | 51 | 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. 52 | 53 | The SPUserResizableView is customizable using the following properties: 54 | 55 | ``` objective-c 56 | @property (nonatomic) CGFloat minWidth; 57 | @property (nonatomic) CGFloat minHeight; 58 | @property (nonatomic) BOOL preventsPositionOutsideSuperview; 59 | ``` 60 | 61 | For an example of how to use SPUserResizableView, please see the included example project. 62 | 63 | -------------------------------------------------------------------------------- /SPUserResizableView/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // SPInteractiveLabel 4 | // 5 | // Created by Stephen Poletto on 12/10/11. 6 | // 7 | 8 | #import "ViewController.h" 9 | 10 | @implementation ViewController 11 | 12 | - (void)viewDidLoad { 13 | CGRect appFrame = [[UIScreen mainScreen] applicationFrame]; 14 | self.view = [[UIView alloc] initWithFrame:appFrame]; 15 | self.view.backgroundColor = [UIColor greenColor]; 16 | 17 | // (1) Create a user resizable view with a simple red background content view. 18 | CGRect gripFrame = CGRectMake(50, 50, 200, 150); 19 | SPUserResizableView *userResizableView = [[SPUserResizableView alloc] initWithFrame:gripFrame]; 20 | UIView *contentView = [[UIView alloc] initWithFrame:gripFrame]; 21 | [contentView setBackgroundColor:[UIColor redColor]]; 22 | userResizableView.contentView = contentView; 23 | userResizableView.delegate = self; 24 | [userResizableView showEditingHandles]; 25 | currentlyEditingView = userResizableView; 26 | lastEditedView = userResizableView; 27 | [self.view addSubview:userResizableView]; 28 | [contentView release]; [userResizableView release]; 29 | 30 | // (2) Create a second resizable view with a UIImageView as the content. 31 | CGRect imageFrame = CGRectMake(50, 200, 200, 200); 32 | SPUserResizableView *imageResizableView = [[SPUserResizableView alloc] initWithFrame:imageFrame]; 33 | UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"milky_way.jpg"]]; 34 | imageResizableView.contentView = imageView; 35 | imageResizableView.delegate = self; 36 | [self.view addSubview:imageResizableView]; 37 | [imageView release]; [imageResizableView release]; 38 | 39 | UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(hideEditingHandles)]; 40 | [gestureRecognizer setDelegate:self]; 41 | [self.view addGestureRecognizer:gestureRecognizer]; 42 | [gestureRecognizer release]; 43 | } 44 | 45 | - (void)userResizableViewDidBeginEditing:(SPUserResizableView *)userResizableView { 46 | [currentlyEditingView hideEditingHandles]; 47 | currentlyEditingView = userResizableView; 48 | } 49 | 50 | - (void)userResizableViewDidEndEditing:(SPUserResizableView *)userResizableView { 51 | lastEditedView = userResizableView; 52 | } 53 | 54 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { 55 | if ([currentlyEditingView hitTest:[touch locationInView:currentlyEditingView] withEvent:nil]) { 56 | return NO; 57 | } 58 | return YES; 59 | } 60 | 61 | - (void)hideEditingHandles { 62 | // We only want the gesture recognizer to end the editing session on the last 63 | // edited view. We wouldn't want to dismiss an editing session in progress. 64 | [lastEditedView hideEditingHandles]; 65 | } 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /SPUserResizableView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 9C36DD3A1494491A00D3035F /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9C36DD391494491A00D3035F /* UIKit.framework */; }; 11 | 9C36DD3C1494491A00D3035F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9C36DD3B1494491A00D3035F /* Foundation.framework */; }; 12 | 9C36DD3E1494491A00D3035F /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9C36DD3D1494491A00D3035F /* CoreGraphics.framework */; }; 13 | 9C36DD441494491A00D3035F /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9C36DD421494491A00D3035F /* InfoPlist.strings */; }; 14 | 9C36DD461494491B00D3035F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C36DD451494491B00D3035F /* main.m */; }; 15 | 9C36DD4A1494491B00D3035F /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C36DD491494491B00D3035F /* AppDelegate.m */; }; 16 | 9C36DD5414944A8F00D3035F /* SPUserResizableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C36DD5114944A8F00D3035F /* SPUserResizableView.m */; }; 17 | 9C36DD5514944A8F00D3035F /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C36DD5314944A8F00D3035F /* ViewController.m */; }; 18 | 9C36DD5714944F1600D3035F /* milky_way.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 9C36DD5614944F1600D3035F /* milky_way.jpg */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | 9C36DD351494491A00D3035F /* SPUserResizableView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SPUserResizableView.app; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | 9C36DD391494491A00D3035F /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 24 | 9C36DD3B1494491A00D3035F /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 25 | 9C36DD3D1494491A00D3035F /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 26 | 9C36DD411494491A00D3035F /* SPUserResizableView-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SPUserResizableView-Info.plist"; sourceTree = ""; }; 27 | 9C36DD431494491A00D3035F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 28 | 9C36DD451494491B00D3035F /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 29 | 9C36DD471494491B00D3035F /* SPUserResizableView-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SPUserResizableView-Prefix.pch"; sourceTree = ""; }; 30 | 9C36DD481494491B00D3035F /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 31 | 9C36DD491494491B00D3035F /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 32 | 9C36DD5014944A8F00D3035F /* SPUserResizableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPUserResizableView.h; sourceTree = ""; }; 33 | 9C36DD5114944A8F00D3035F /* SPUserResizableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPUserResizableView.m; sourceTree = ""; }; 34 | 9C36DD5214944A8F00D3035F /* ViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 35 | 9C36DD5314944A8F00D3035F /* ViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 36 | 9C36DD5614944F1600D3035F /* milky_way.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = milky_way.jpg; sourceTree = ""; }; 37 | /* End PBXFileReference section */ 38 | 39 | /* Begin PBXFrameworksBuildPhase section */ 40 | 9C36DD321494491A00D3035F /* Frameworks */ = { 41 | isa = PBXFrameworksBuildPhase; 42 | buildActionMask = 2147483647; 43 | files = ( 44 | 9C36DD3A1494491A00D3035F /* UIKit.framework in Frameworks */, 45 | 9C36DD3C1494491A00D3035F /* Foundation.framework in Frameworks */, 46 | 9C36DD3E1494491A00D3035F /* CoreGraphics.framework in Frameworks */, 47 | ); 48 | runOnlyForDeploymentPostprocessing = 0; 49 | }; 50 | /* End PBXFrameworksBuildPhase section */ 51 | 52 | /* Begin PBXGroup section */ 53 | 9C36DD2A1494491A00D3035F = { 54 | isa = PBXGroup; 55 | children = ( 56 | 9C36DD3F1494491A00D3035F /* SPUserResizableView */, 57 | 9C36DD381494491A00D3035F /* Frameworks */, 58 | 9C36DD361494491A00D3035F /* Products */, 59 | ); 60 | sourceTree = ""; 61 | }; 62 | 9C36DD361494491A00D3035F /* Products */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | 9C36DD351494491A00D3035F /* SPUserResizableView.app */, 66 | ); 67 | name = Products; 68 | sourceTree = ""; 69 | }; 70 | 9C36DD381494491A00D3035F /* Frameworks */ = { 71 | isa = PBXGroup; 72 | children = ( 73 | 9C36DD391494491A00D3035F /* UIKit.framework */, 74 | 9C36DD3B1494491A00D3035F /* Foundation.framework */, 75 | 9C36DD3D1494491A00D3035F /* CoreGraphics.framework */, 76 | ); 77 | name = Frameworks; 78 | sourceTree = ""; 79 | }; 80 | 9C36DD3F1494491A00D3035F /* SPUserResizableView */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | 9C36DD5014944A8F00D3035F /* SPUserResizableView.h */, 84 | 9C36DD5114944A8F00D3035F /* SPUserResizableView.m */, 85 | 9C36DD5214944A8F00D3035F /* ViewController.h */, 86 | 9C36DD5314944A8F00D3035F /* ViewController.m */, 87 | 9C36DD481494491B00D3035F /* AppDelegate.h */, 88 | 9C36DD491494491B00D3035F /* AppDelegate.m */, 89 | 9C36DD401494491A00D3035F /* Supporting Files */, 90 | ); 91 | path = SPUserResizableView; 92 | sourceTree = ""; 93 | }; 94 | 9C36DD401494491A00D3035F /* Supporting Files */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 9C36DD5614944F1600D3035F /* milky_way.jpg */, 98 | 9C36DD411494491A00D3035F /* SPUserResizableView-Info.plist */, 99 | 9C36DD421494491A00D3035F /* InfoPlist.strings */, 100 | 9C36DD451494491B00D3035F /* main.m */, 101 | 9C36DD471494491B00D3035F /* SPUserResizableView-Prefix.pch */, 102 | ); 103 | name = "Supporting Files"; 104 | sourceTree = ""; 105 | }; 106 | /* End PBXGroup section */ 107 | 108 | /* Begin PBXNativeTarget section */ 109 | 9C36DD341494491A00D3035F /* SPUserResizableView */ = { 110 | isa = PBXNativeTarget; 111 | buildConfigurationList = 9C36DD4D1494491B00D3035F /* Build configuration list for PBXNativeTarget "SPUserResizableView" */; 112 | buildPhases = ( 113 | 9C36DD311494491A00D3035F /* Sources */, 114 | 9C36DD321494491A00D3035F /* Frameworks */, 115 | 9C36DD331494491A00D3035F /* Resources */, 116 | ); 117 | buildRules = ( 118 | ); 119 | dependencies = ( 120 | ); 121 | name = SPUserResizableView; 122 | productName = SPUserResizableView; 123 | productReference = 9C36DD351494491A00D3035F /* SPUserResizableView.app */; 124 | productType = "com.apple.product-type.application"; 125 | }; 126 | /* End PBXNativeTarget section */ 127 | 128 | /* Begin PBXProject section */ 129 | 9C36DD2C1494491A00D3035F /* Project object */ = { 130 | isa = PBXProject; 131 | attributes = { 132 | LastUpgradeCheck = 0420; 133 | ORGANIZATIONNAME = "Brown University"; 134 | }; 135 | buildConfigurationList = 9C36DD2F1494491A00D3035F /* Build configuration list for PBXProject "SPUserResizableView" */; 136 | compatibilityVersion = "Xcode 3.2"; 137 | developmentRegion = English; 138 | hasScannedForEncodings = 0; 139 | knownRegions = ( 140 | en, 141 | ); 142 | mainGroup = 9C36DD2A1494491A00D3035F; 143 | productRefGroup = 9C36DD361494491A00D3035F /* Products */; 144 | projectDirPath = ""; 145 | projectRoot = ""; 146 | targets = ( 147 | 9C36DD341494491A00D3035F /* SPUserResizableView */, 148 | ); 149 | }; 150 | /* End PBXProject section */ 151 | 152 | /* Begin PBXResourcesBuildPhase section */ 153 | 9C36DD331494491A00D3035F /* Resources */ = { 154 | isa = PBXResourcesBuildPhase; 155 | buildActionMask = 2147483647; 156 | files = ( 157 | 9C36DD441494491A00D3035F /* InfoPlist.strings in Resources */, 158 | 9C36DD5714944F1600D3035F /* milky_way.jpg in Resources */, 159 | ); 160 | runOnlyForDeploymentPostprocessing = 0; 161 | }; 162 | /* End PBXResourcesBuildPhase section */ 163 | 164 | /* Begin PBXSourcesBuildPhase section */ 165 | 9C36DD311494491A00D3035F /* Sources */ = { 166 | isa = PBXSourcesBuildPhase; 167 | buildActionMask = 2147483647; 168 | files = ( 169 | 9C36DD461494491B00D3035F /* main.m in Sources */, 170 | 9C36DD4A1494491B00D3035F /* AppDelegate.m in Sources */, 171 | 9C36DD5414944A8F00D3035F /* SPUserResizableView.m in Sources */, 172 | 9C36DD5514944A8F00D3035F /* ViewController.m in Sources */, 173 | ); 174 | runOnlyForDeploymentPostprocessing = 0; 175 | }; 176 | /* End PBXSourcesBuildPhase section */ 177 | 178 | /* Begin PBXVariantGroup section */ 179 | 9C36DD421494491A00D3035F /* InfoPlist.strings */ = { 180 | isa = PBXVariantGroup; 181 | children = ( 182 | 9C36DD431494491A00D3035F /* en */, 183 | ); 184 | name = InfoPlist.strings; 185 | sourceTree = ""; 186 | }; 187 | /* End PBXVariantGroup section */ 188 | 189 | /* Begin XCBuildConfiguration section */ 190 | 9C36DD4B1494491B00D3035F /* Debug */ = { 191 | isa = XCBuildConfiguration; 192 | buildSettings = { 193 | ALWAYS_SEARCH_USER_PATHS = NO; 194 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 195 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 196 | COPY_PHASE_STRIP = NO; 197 | GCC_C_LANGUAGE_STANDARD = gnu99; 198 | GCC_DYNAMIC_NO_PIC = NO; 199 | GCC_OPTIMIZATION_LEVEL = 0; 200 | GCC_PREPROCESSOR_DEFINITIONS = ( 201 | "DEBUG=1", 202 | "$(inherited)", 203 | ); 204 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 205 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 206 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 207 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 208 | GCC_WARN_UNUSED_VARIABLE = YES; 209 | IPHONEOS_DEPLOYMENT_TARGET = 5.0; 210 | SDKROOT = iphoneos; 211 | }; 212 | name = Debug; 213 | }; 214 | 9C36DD4C1494491B00D3035F /* Release */ = { 215 | isa = XCBuildConfiguration; 216 | buildSettings = { 217 | ALWAYS_SEARCH_USER_PATHS = NO; 218 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 219 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 220 | COPY_PHASE_STRIP = YES; 221 | GCC_C_LANGUAGE_STANDARD = gnu99; 222 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 223 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 224 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 225 | GCC_WARN_UNUSED_VARIABLE = YES; 226 | IPHONEOS_DEPLOYMENT_TARGET = 5.0; 227 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 228 | SDKROOT = iphoneos; 229 | VALIDATE_PRODUCT = YES; 230 | }; 231 | name = Release; 232 | }; 233 | 9C36DD4E1494491B00D3035F /* Debug */ = { 234 | isa = XCBuildConfiguration; 235 | buildSettings = { 236 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 237 | GCC_PREFIX_HEADER = "SPUserResizableView/SPUserResizableView-Prefix.pch"; 238 | INFOPLIST_FILE = "SPUserResizableView/SPUserResizableView-Info.plist"; 239 | PRODUCT_NAME = "$(TARGET_NAME)"; 240 | WRAPPER_EXTENSION = app; 241 | }; 242 | name = Debug; 243 | }; 244 | 9C36DD4F1494491B00D3035F /* Release */ = { 245 | isa = XCBuildConfiguration; 246 | buildSettings = { 247 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 248 | GCC_PREFIX_HEADER = "SPUserResizableView/SPUserResizableView-Prefix.pch"; 249 | INFOPLIST_FILE = "SPUserResizableView/SPUserResizableView-Info.plist"; 250 | PRODUCT_NAME = "$(TARGET_NAME)"; 251 | WRAPPER_EXTENSION = app; 252 | }; 253 | name = Release; 254 | }; 255 | /* End XCBuildConfiguration section */ 256 | 257 | /* Begin XCConfigurationList section */ 258 | 9C36DD2F1494491A00D3035F /* Build configuration list for PBXProject "SPUserResizableView" */ = { 259 | isa = XCConfigurationList; 260 | buildConfigurations = ( 261 | 9C36DD4B1494491B00D3035F /* Debug */, 262 | 9C36DD4C1494491B00D3035F /* Release */, 263 | ); 264 | defaultConfigurationIsVisible = 0; 265 | defaultConfigurationName = Release; 266 | }; 267 | 9C36DD4D1494491B00D3035F /* Build configuration list for PBXNativeTarget "SPUserResizableView" */ = { 268 | isa = XCConfigurationList; 269 | buildConfigurations = ( 270 | 9C36DD4E1494491B00D3035F /* Debug */, 271 | 9C36DD4F1494491B00D3035F /* Release */, 272 | ); 273 | defaultConfigurationIsVisible = 0; 274 | }; 275 | /* End XCConfigurationList section */ 276 | }; 277 | rootObject = 9C36DD2C1494491A00D3035F /* Project object */; 278 | } 279 | -------------------------------------------------------------------------------- /SPUserResizableView/SPUserResizableView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPUserResizableView.m 3 | // SPUserResizableView 4 | // 5 | // Created by Stephen Poletto on 12/10/11. 6 | // 7 | 8 | #import "SPUserResizableView.h" 9 | 10 | /* Let's inset everything that's drawn (the handles and the content view) 11 | so that users can trigger a resize from a few pixels outside of 12 | what they actually see as the bounding box. */ 13 | #define kSPUserResizableViewGlobalInset 5.0 14 | 15 | #define kSPUserResizableViewDefaultMinWidth 48.0 16 | #define kSPUserResizableViewDefaultMinHeight 48.0 17 | #define kSPUserResizableViewInteractiveBorderSize 10.0 18 | 19 | static SPUserResizableViewAnchorPoint SPUserResizableViewNoResizeAnchorPoint = { 0.0, 0.0, 0.0, 0.0 }; 20 | static SPUserResizableViewAnchorPoint SPUserResizableViewUpperLeftAnchorPoint = { 1.0, 1.0, -1.0, 1.0 }; 21 | static SPUserResizableViewAnchorPoint SPUserResizableViewMiddleLeftAnchorPoint = { 1.0, 0.0, 0.0, 1.0 }; 22 | static SPUserResizableViewAnchorPoint SPUserResizableViewLowerLeftAnchorPoint = { 1.0, 0.0, 1.0, 1.0 }; 23 | static SPUserResizableViewAnchorPoint SPUserResizableViewUpperMiddleAnchorPoint = { 0.0, 1.0, -1.0, 0.0 }; 24 | static SPUserResizableViewAnchorPoint SPUserResizableViewUpperRightAnchorPoint = { 0.0, 1.0, -1.0, -1.0 }; 25 | static SPUserResizableViewAnchorPoint SPUserResizableViewMiddleRightAnchorPoint = { 0.0, 0.0, 0.0, -1.0 }; 26 | static SPUserResizableViewAnchorPoint SPUserResizableViewLowerRightAnchorPoint = { 0.0, 0.0, 1.0, -1.0 }; 27 | static SPUserResizableViewAnchorPoint SPUserResizableViewLowerMiddleAnchorPoint = { 0.0, 0.0, 1.0, 0.0 }; 28 | 29 | @interface SPGripViewBorderView : UIView 30 | @end 31 | 32 | @implementation SPGripViewBorderView 33 | 34 | - (id)initWithFrame:(CGRect)frame { 35 | if ((self = [super initWithFrame:frame])) { 36 | // Clear background to ensure the content view shows through. 37 | self.backgroundColor = [UIColor clearColor]; 38 | } 39 | return self; 40 | } 41 | 42 | - (void)drawRect:(CGRect)rect { 43 | CGContextRef context = UIGraphicsGetCurrentContext(); 44 | CGContextSaveGState(context); 45 | 46 | // (1) Draw the bounding box. 47 | CGContextSetLineWidth(context, 1.0); 48 | CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor); 49 | CGContextAddRect(context, CGRectInset(self.bounds, kSPUserResizableViewInteractiveBorderSize/2, kSPUserResizableViewInteractiveBorderSize/2)); 50 | CGContextStrokePath(context); 51 | 52 | // (2) Calculate the bounding boxes for each of the anchor points. 53 | CGRect upperLeft = CGRectMake(0.0, 0.0, kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize); 54 | CGRect upperRight = CGRectMake(self.bounds.size.width - kSPUserResizableViewInteractiveBorderSize, 0.0, kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize); 55 | CGRect lowerRight = CGRectMake(self.bounds.size.width - kSPUserResizableViewInteractiveBorderSize, self.bounds.size.height - kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize); 56 | CGRect lowerLeft = CGRectMake(0.0, self.bounds.size.height - kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize); 57 | CGRect upperMiddle = CGRectMake((self.bounds.size.width - kSPUserResizableViewInteractiveBorderSize)/2, 0.0, kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize); 58 | CGRect lowerMiddle = CGRectMake((self.bounds.size.width - kSPUserResizableViewInteractiveBorderSize)/2, self.bounds.size.height - kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize); 59 | CGRect middleLeft = CGRectMake(0.0, (self.bounds.size.height - kSPUserResizableViewInteractiveBorderSize)/2, kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize); 60 | CGRect middleRight = CGRectMake(self.bounds.size.width - kSPUserResizableViewInteractiveBorderSize, (self.bounds.size.height - kSPUserResizableViewInteractiveBorderSize)/2, kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize); 61 | 62 | // (3) Create the gradient to paint the anchor points. 63 | CGFloat colors [] = { 64 | 0.4, 0.8, 1.0, 1.0, 65 | 0.0, 0.0, 1.0, 1.0 66 | }; 67 | CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceRGB(); 68 | CGGradientRef gradient = CGGradientCreateWithColorComponents(baseSpace, colors, NULL, 2); 69 | CGColorSpaceRelease(baseSpace), baseSpace = NULL; 70 | 71 | // (4) Set up the stroke for drawing the border of each of the anchor points. 72 | CGContextSetLineWidth(context, 1); 73 | CGContextSetShadow(context, CGSizeMake(0.5, 0.5), 1); 74 | CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor); 75 | 76 | // (5) Fill each anchor point using the gradient, then stroke the border. 77 | CGRect allPoints[8] = { upperLeft, upperRight, lowerRight, lowerLeft, upperMiddle, lowerMiddle, middleLeft, middleRight }; 78 | for (NSInteger i = 0; i < 8; i++) { 79 | CGRect currPoint = allPoints[i]; 80 | CGContextSaveGState(context); 81 | CGContextAddEllipseInRect(context, currPoint); 82 | CGContextClip(context); 83 | CGPoint startPoint = CGPointMake(CGRectGetMidX(currPoint), CGRectGetMinY(currPoint)); 84 | CGPoint endPoint = CGPointMake(CGRectGetMidX(currPoint), CGRectGetMaxY(currPoint)); 85 | CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0); 86 | CGContextRestoreGState(context); 87 | CGContextStrokeEllipseInRect(context, CGRectInset(currPoint, 1, 1)); 88 | } 89 | CGGradientRelease(gradient), gradient = NULL; 90 | CGContextRestoreGState(context); 91 | } 92 | 93 | @end 94 | 95 | @implementation SPUserResizableView 96 | 97 | @synthesize contentView, minWidth, minHeight, preventsPositionOutsideSuperview, delegate; 98 | 99 | - (void)setupDefaultAttributes { 100 | borderView = [[SPGripViewBorderView alloc] initWithFrame:CGRectInset(self.bounds, kSPUserResizableViewGlobalInset, kSPUserResizableViewGlobalInset)]; 101 | [borderView setHidden:YES]; 102 | [self addSubview:borderView]; 103 | self.minWidth = kSPUserResizableViewDefaultMinWidth; 104 | self.minHeight = kSPUserResizableViewDefaultMinHeight; 105 | self.preventsPositionOutsideSuperview = YES; 106 | } 107 | 108 | - (id)initWithFrame:(CGRect)frame { 109 | if ((self = [super initWithFrame:frame])) { 110 | [self setupDefaultAttributes]; 111 | } 112 | return self; 113 | } 114 | 115 | - (id)initWithCoder:(NSCoder *)aDecoder { 116 | if ((self = [super initWithCoder:aDecoder])) { 117 | [self setupDefaultAttributes]; 118 | } 119 | return self; 120 | } 121 | 122 | - (void)setContentView:(UIView *)newContentView { 123 | [contentView removeFromSuperview]; 124 | contentView = newContentView; 125 | contentView.frame = CGRectInset(self.bounds, kSPUserResizableViewGlobalInset + kSPUserResizableViewInteractiveBorderSize/2, kSPUserResizableViewGlobalInset + kSPUserResizableViewInteractiveBorderSize/2); 126 | [self addSubview:contentView]; 127 | 128 | // Ensure the border view is always on top by removing it and adding it to the end of the subview list. 129 | [borderView removeFromSuperview]; 130 | [self addSubview:borderView]; 131 | } 132 | 133 | - (void)setFrame:(CGRect)newFrame { 134 | [super setFrame:newFrame]; 135 | contentView.frame = CGRectInset(self.bounds, kSPUserResizableViewGlobalInset + kSPUserResizableViewInteractiveBorderSize/2, kSPUserResizableViewGlobalInset + kSPUserResizableViewInteractiveBorderSize/2); 136 | borderView.frame = CGRectInset(self.bounds, kSPUserResizableViewGlobalInset, kSPUserResizableViewGlobalInset); 137 | [borderView setNeedsDisplay]; 138 | } 139 | 140 | static CGFloat SPDistanceBetweenTwoPoints(CGPoint point1, CGPoint point2) { 141 | CGFloat dx = point2.x - point1.x; 142 | CGFloat dy = point2.y - point1.y; 143 | return sqrt(dx*dx + dy*dy); 144 | }; 145 | 146 | typedef struct CGPointSPUserResizableViewAnchorPointPair { 147 | CGPoint point; 148 | SPUserResizableViewAnchorPoint anchorPoint; 149 | } CGPointSPUserResizableViewAnchorPointPair; 150 | 151 | - (SPUserResizableViewAnchorPoint)anchorPointForTouchLocation:(CGPoint)touchPoint { 152 | // (1) Calculate the positions of each of the anchor points. 153 | CGPointSPUserResizableViewAnchorPointPair upperLeft = { CGPointMake(0.0, 0.0), SPUserResizableViewUpperLeftAnchorPoint }; 154 | CGPointSPUserResizableViewAnchorPointPair upperMiddle = { CGPointMake(self.bounds.size.width/2, 0.0), SPUserResizableViewUpperMiddleAnchorPoint }; 155 | CGPointSPUserResizableViewAnchorPointPair upperRight = { CGPointMake(self.bounds.size.width, 0.0), SPUserResizableViewUpperRightAnchorPoint }; 156 | CGPointSPUserResizableViewAnchorPointPair middleRight = { CGPointMake(self.bounds.size.width, self.bounds.size.height/2), SPUserResizableViewMiddleRightAnchorPoint }; 157 | CGPointSPUserResizableViewAnchorPointPair lowerRight = { CGPointMake(self.bounds.size.width, self.bounds.size.height), SPUserResizableViewLowerRightAnchorPoint }; 158 | CGPointSPUserResizableViewAnchorPointPair lowerMiddle = { CGPointMake(self.bounds.size.width/2, self.bounds.size.height), SPUserResizableViewLowerMiddleAnchorPoint }; 159 | CGPointSPUserResizableViewAnchorPointPair lowerLeft = { CGPointMake(0, self.bounds.size.height), SPUserResizableViewLowerLeftAnchorPoint }; 160 | CGPointSPUserResizableViewAnchorPointPair middleLeft = { CGPointMake(0, self.bounds.size.height/2), SPUserResizableViewMiddleLeftAnchorPoint }; 161 | CGPointSPUserResizableViewAnchorPointPair centerPoint = { CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2), SPUserResizableViewNoResizeAnchorPoint }; 162 | 163 | // (2) Iterate over each of the anchor points and find the one closest to the user's touch. 164 | CGPointSPUserResizableViewAnchorPointPair allPoints[9] = { upperLeft, upperRight, lowerRight, lowerLeft, upperMiddle, lowerMiddle, middleLeft, middleRight, centerPoint }; 165 | CGFloat smallestDistance = MAXFLOAT; CGPointSPUserResizableViewAnchorPointPair closestPoint = centerPoint; 166 | for (NSInteger i = 0; i < 9; i++) { 167 | CGFloat distance = SPDistanceBetweenTwoPoints(touchPoint, allPoints[i].point); 168 | if (distance < smallestDistance) { 169 | closestPoint = allPoints[i]; 170 | smallestDistance = distance; 171 | } 172 | } 173 | return closestPoint.anchorPoint; 174 | } 175 | 176 | - (BOOL)isResizing { 177 | return (anchorPoint.adjustsH || anchorPoint.adjustsW || anchorPoint.adjustsX || anchorPoint.adjustsY); 178 | } 179 | 180 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 181 | // Notify the delegate we've begun our editing session. 182 | if (self.delegate && [self.delegate respondsToSelector:@selector(userResizableViewDidBeginEditing:)]) { 183 | [self.delegate userResizableViewDidBeginEditing:self]; 184 | } 185 | 186 | [borderView setHidden:NO]; 187 | UITouch *touch = [touches anyObject]; 188 | anchorPoint = [self anchorPointForTouchLocation:[touch locationInView:self]]; 189 | 190 | // When resizing, all calculations are done in the superview's coordinate space. 191 | touchStart = [touch locationInView:self.superview]; 192 | if (![self isResizing]) { 193 | // When translating, all calculations are done in the view's coordinate space. 194 | touchStart = [touch locationInView:self]; 195 | } 196 | } 197 | 198 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { 199 | // Notify the delegate we've ended our editing session. 200 | if (self.delegate && [self.delegate respondsToSelector:@selector(userResizableViewDidEndEditing:)]) { 201 | [self.delegate userResizableViewDidEndEditing:self]; 202 | } 203 | } 204 | 205 | - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { 206 | // Notify the delegate we've ended our editing session. 207 | if (self.delegate && [self.delegate respondsToSelector:@selector(userResizableViewDidEndEditing:)]) { 208 | [self.delegate userResizableViewDidEndEditing:self]; 209 | } 210 | } 211 | 212 | - (void)showEditingHandles { 213 | [borderView setHidden:NO]; 214 | } 215 | 216 | - (void)hideEditingHandles { 217 | [borderView setHidden:YES]; 218 | } 219 | 220 | - (void)resizeUsingTouchLocation:(CGPoint)touchPoint { 221 | // (1) Update the touch point if we're outside the superview. 222 | if (self.preventsPositionOutsideSuperview) { 223 | CGFloat border = kSPUserResizableViewGlobalInset + kSPUserResizableViewInteractiveBorderSize/2; 224 | if (touchPoint.x < border) { 225 | touchPoint.x = border; 226 | } 227 | if (touchPoint.x > self.superview.bounds.size.width - border) { 228 | touchPoint.x = self.superview.bounds.size.width - border; 229 | } 230 | if (touchPoint.y < border) { 231 | touchPoint.y = border; 232 | } 233 | if (touchPoint.y > self.superview.bounds.size.height - border) { 234 | touchPoint.y = self.superview.bounds.size.height - border; 235 | } 236 | } 237 | 238 | // (2) Calculate the deltas using the current anchor point. 239 | CGFloat deltaW = anchorPoint.adjustsW * (touchStart.x - touchPoint.x); 240 | CGFloat deltaX = anchorPoint.adjustsX * (-1.0 * deltaW); 241 | CGFloat deltaH = anchorPoint.adjustsH * (touchPoint.y - touchStart.y); 242 | CGFloat deltaY = anchorPoint.adjustsY * (-1.0 * deltaH); 243 | 244 | // (3) Calculate the new frame. 245 | CGFloat newX = self.frame.origin.x + deltaX; 246 | CGFloat newY = self.frame.origin.y + deltaY; 247 | CGFloat newWidth = self.frame.size.width + deltaW; 248 | CGFloat newHeight = self.frame.size.height + deltaH; 249 | 250 | // (4) If the new frame is too small, cancel the changes. 251 | if (newWidth < self.minWidth) { 252 | newWidth = self.frame.size.width; 253 | newX = self.frame.origin.x; 254 | } 255 | if (newHeight < self.minHeight) { 256 | newHeight = self.frame.size.height; 257 | newY = self.frame.origin.y; 258 | } 259 | 260 | // (5) Ensure the resize won't cause the view to move offscreen. 261 | if (self.preventsPositionOutsideSuperview) { 262 | if (newX < self.superview.bounds.origin.x) { 263 | // Calculate how much to grow the width by such that the new X coordintae will align with the superview. 264 | deltaW = self.frame.origin.x - self.superview.bounds.origin.x; 265 | newWidth = self.frame.size.width + deltaW; 266 | newX = self.superview.bounds.origin.x; 267 | } 268 | if (newX + newWidth > self.superview.bounds.origin.x + self.superview.bounds.size.width) { 269 | newWidth = self.superview.bounds.size.width - newX; 270 | } 271 | if (newY < self.superview.bounds.origin.y) { 272 | // Calculate how much to grow the height by such that the new Y coordintae will align with the superview. 273 | deltaH = self.frame.origin.y - self.superview.bounds.origin.y; 274 | newHeight = self.frame.size.height + deltaH; 275 | newY = self.superview.bounds.origin.y; 276 | } 277 | if (newY + newHeight > self.superview.bounds.origin.y + self.superview.bounds.size.height) { 278 | newHeight = self.superview.bounds.size.height - newY; 279 | } 280 | } 281 | 282 | self.frame = CGRectMake(newX, newY, newWidth, newHeight); 283 | touchStart = touchPoint; 284 | } 285 | 286 | - (void)translateUsingTouchLocation:(CGPoint)touchPoint { 287 | CGPoint newCenter = CGPointMake(self.center.x + touchPoint.x - touchStart.x, self.center.y + touchPoint.y - touchStart.y); 288 | if (self.preventsPositionOutsideSuperview) { 289 | // Ensure the translation won't cause the view to move offscreen. 290 | CGFloat midPointX = CGRectGetMidX(self.bounds); 291 | if (newCenter.x > self.superview.bounds.size.width - midPointX) { 292 | newCenter.x = self.superview.bounds.size.width - midPointX; 293 | } 294 | if (newCenter.x < midPointX) { 295 | newCenter.x = midPointX; 296 | } 297 | CGFloat midPointY = CGRectGetMidY(self.bounds); 298 | if (newCenter.y > self.superview.bounds.size.height - midPointY) { 299 | newCenter.y = self.superview.bounds.size.height - midPointY; 300 | } 301 | if (newCenter.y < midPointY) { 302 | newCenter.y = midPointY; 303 | } 304 | } 305 | self.center = newCenter; 306 | } 307 | 308 | - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { 309 | if ([self isResizing]) { 310 | [self resizeUsingTouchLocation:[[touches anyObject] locationInView:self.superview]]; 311 | } else { 312 | [self translateUsingTouchLocation:[[touches anyObject] locationInView:self]]; 313 | } 314 | } 315 | 316 | - (void)dealloc { 317 | [contentView removeFromSuperview]; 318 | [borderView release]; 319 | [super dealloc]; 320 | } 321 | 322 | @end 323 | --------------------------------------------------------------------------------