├── screenshot.png
├── RKUserResizableViewDemo
├── RKUserResizableViewDemo
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ ├── smiley.imageset
│ │ │ ├── smiley.jpg
│ │ │ └── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── Info.plist
│ ├── Base.lproj
│ │ ├── Main.storyboard
│ │ └── LaunchScreen.storyboard
│ ├── AppDelegate.swift
│ ├── ViewController.swift
│ └── RKUserResizableView.swift
└── RKUserResizableViewDemo.xcodeproj
│ ├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcuserdata
│ │ └── user.xcuserdatad
│ │ └── UserInterfaceState.xcuserstate
│ ├── xcuserdata
│ └── user.xcuserdatad
│ │ └── xcschemes
│ │ ├── xcschememanagement.plist
│ │ └── RKUserResizableViewDemo.xcscheme
│ └── project.pbxproj
├── LICENSE
├── README.md
└── RKUserResizableView.swift
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RajatJain4061/RKUserResizableView/HEAD/screenshot.png
--------------------------------------------------------------------------------
/RKUserResizableViewDemo/RKUserResizableViewDemo/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/RKUserResizableViewDemo/RKUserResizableViewDemo/Assets.xcassets/smiley.imageset/smiley.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RajatJain4061/RKUserResizableView/HEAD/RKUserResizableViewDemo/RKUserResizableViewDemo/Assets.xcassets/smiley.imageset/smiley.jpg
--------------------------------------------------------------------------------
/RKUserResizableViewDemo/RKUserResizableViewDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/RKUserResizableViewDemo/RKUserResizableViewDemo.xcodeproj/project.xcworkspace/xcuserdata/user.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RajatJain4061/RKUserResizableView/HEAD/RKUserResizableViewDemo/RKUserResizableViewDemo.xcodeproj/project.xcworkspace/xcuserdata/user.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/RKUserResizableViewDemo/RKUserResizableViewDemo/Assets.xcassets/smiley.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "smiley.jpg",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/RKUserResizableViewDemo/RKUserResizableViewDemo.xcodeproj/xcuserdata/user.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | RKUserResizableViewDemo.xcscheme
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | CEE4F5FC1F5675B300C12FF8
16 |
17 | primary
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Rajat jain
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/RKUserResizableViewDemo/RKUserResizableViewDemo/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "ipad",
5 | "size" : "20x20",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "ipad",
10 | "size" : "20x20",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "ipad",
15 | "size" : "29x29",
16 | "scale" : "1x"
17 | },
18 | {
19 | "idiom" : "ipad",
20 | "size" : "29x29",
21 | "scale" : "2x"
22 | },
23 | {
24 | "idiom" : "ipad",
25 | "size" : "40x40",
26 | "scale" : "1x"
27 | },
28 | {
29 | "idiom" : "ipad",
30 | "size" : "40x40",
31 | "scale" : "2x"
32 | },
33 | {
34 | "idiom" : "ipad",
35 | "size" : "76x76",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "ipad",
40 | "size" : "76x76",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "83.5x83.5",
46 | "scale" : "2x"
47 | }
48 | ],
49 | "info" : {
50 | "version" : 1,
51 | "author" : "xcode"
52 | }
53 | }
--------------------------------------------------------------------------------
/RKUserResizableViewDemo/RKUserResizableViewDemo/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations~ipad
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationPortraitUpsideDown
35 | UIInterfaceOrientationLandscapeLeft
36 | UIInterfaceOrientationLandscapeRight
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/RKUserResizableViewDemo/RKUserResizableViewDemo/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/RKUserResizableViewDemo/RKUserResizableViewDemo/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/RKUserResizableViewDemo/RKUserResizableViewDemo/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // RKUserResizableViewDemo
4 | //
5 | // Created by user on 30/08/17.
6 | // Copyright © 2017 rk. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
18 | // Override point for customization after application launch.
19 | return true
20 | }
21 |
22 | func applicationWillResignActive(_ application: UIApplication) {
23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
25 | }
26 |
27 | func applicationDidEnterBackground(_ application: UIApplication) {
28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
30 | }
31 |
32 | func applicationWillEnterForeground(_ application: UIApplication) {
33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
34 | }
35 |
36 | func applicationDidBecomeActive(_ application: UIApplication) {
37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
38 | }
39 |
40 | func applicationWillTerminate(_ application: UIApplication) {
41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
42 | }
43 |
44 |
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/RKUserResizableViewDemo/RKUserResizableViewDemo/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // RKUserResizableViewDemo
4 | //
5 | // Created by user on 30/08/17.
6 | // Copyright © 2017 rk. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ViewController: UIViewController {
12 |
13 | var currentlyEditingView:RKUserResizableView? = nil
14 | var lastEditedView:RKUserResizableView? = nil
15 |
16 | override func viewDidLoad() {
17 | super.viewDidLoad()
18 |
19 | // (1) Create a user resizable view with a simple red background content view.
20 | let gripFrame = CGRect(x: 50, y: 50, width: 200, height: 150)
21 | let userResizableView = RKUserResizableView(frame: gripFrame)
22 | userResizableView.delegate = self
23 | let contentView = UIView(frame: gripFrame)
24 | contentView.backgroundColor = UIColor.red
25 | userResizableView.contentView = contentView
26 | userResizableView.showEditingHandles()
27 | userResizableView.isPreventsPositionOutsideSuperview
28 | currentlyEditingView = userResizableView
29 | lastEditedView = userResizableView
30 | self.view.addSubview(userResizableView)
31 |
32 | // (2) Create a second resizable view with a UIImageView as the content.
33 | let imageFrame = CGRect(x: 50, y: 200, width: 200, height: 200)
34 | let imageResizableView = RKUserResizableView(frame: imageFrame)
35 | let imageView = UIImageView(image: UIImage(named: "smiley"))
36 | imageResizableView.contentView = imageView
37 | imageResizableView.delegate = self
38 | self.view.addSubview(imageResizableView)
39 | }
40 |
41 | override func didReceiveMemoryWarning() {
42 | super.didReceiveMemoryWarning()
43 | // Dispose of any resources that can be recreated.
44 | }
45 |
46 |
47 | }
48 |
49 | extension ViewController:RKUserResizableViewDelegate {
50 | func userResizableViewDidBeginEditing(_ userResizableView: RKUserResizableView) {
51 | currentlyEditingView?.hideEditingHandles()
52 | currentlyEditingView = userResizableView
53 | }
54 |
55 | func userResizableViewDidEndEditing(_ userResizableView: RKUserResizableView) {
56 | lastEditedView = userResizableView
57 | }
58 | }
59 |
60 | extension ViewController:UIGestureRecognizerDelegate {
61 | func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
62 | if ((currentlyEditingView?.hitTest(touch.location(in: currentlyEditingView), with: nil)) != nil) {
63 | return false
64 | }
65 | return true
66 | }
67 |
68 | func hideEditingHandles() {
69 | // We only want the gesture recognizer to end the editing session on the last
70 | // edited view. We wouldn't want to dismiss an editing session in progress.
71 | lastEditedView?.hideEditingHandles()
72 | }
73 | }
74 |
75 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # RKUserResizableView
2 | RKUserResizableView is a user-resizable, user-repositionable UIView subclass built for iOS (Swift 3).
3 |
4 |
5 | RKUserResizableView 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 RKUserResizableView.
6 |
7 | Screenshot
8 | ----
9 | 
10 |
11 | How To Use It
12 | -------------
13 |
14 | ### Installation
15 |
16 | Include RKUserResizableView.swift in your project.
17 |
18 | ### Setting up the RKUserResizableView
19 |
20 | You'll need to construct a new instance of RKUserResizableView. Then, set the contentView on the RKUserResizableView to the view you'd like the user to interact with.
21 |
22 | ``` swift
23 |
24 | var currentlyEditingView:RKUserResizableView? = nil
25 | var lastEditedView:RKUserResizableView? = nil
26 |
27 | override func viewDidLoad() {
28 | super.viewDidLoad()
29 |
30 | // (1) Create a user resizable view with a simple red background content view.
31 | let gripFrame = CGRect(x: 50, y: 50, width: 200, height: 150)
32 | let userResizableView = RKUserResizableView(frame: gripFrame)
33 | userResizableView.delegate = self
34 | let contentView = UIView(frame: gripFrame)
35 | contentView.backgroundColor = UIColor.red
36 | userResizableView.contentView = contentView
37 | userResizableView.showEditingHandles()
38 | currentlyEditingView = userResizableView
39 | lastEditedView = userResizableView
40 | self.view.addSubview(userResizableView)
41 |
42 | // (2) Create a second resizable view with a UIImageView as the content.
43 | let imageFrame = CGRect(x: 50, y: 200, width: 200, height: 200)
44 | let imageResizableView = RKUserResizableView(frame: imageFrame)
45 | let imageView = UIImageView(image: UIImage(named: "smiley"))
46 | imageResizableView.contentView = imageView
47 | imageResizableView.delegate = self
48 | self.view.addSubview(imageResizableView)
49 | }
50 | ```
51 |
52 | If you'd like to receive callbacks when the RKUserResizableView receives touchBegan:, touchesEnded: and touchesCancelled: messages, set the delegate on the RKUserResizableView accordingly.
53 |
54 | ``` swift
55 | userResizableView.delegate = self;
56 | ```
57 |
58 | Then implement the following delegate methods.
59 |
60 | ``` swift
61 | // Called when the resizable view receives touchesBegan: and activates the editing handles.
62 | func userResizableViewDidBeginEditing(_ userResizableView: RKUserResizableView)
63 |
64 | // Called when the resizable view receives touchesEnded: or touchesCancelled:
65 | func userResizableViewDidEndEditing(_ userResizableView: RKUserResizableView)
66 | ```
67 |
68 | By default, RKUserResizableView 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().
69 |
70 | The RKUserResizableView is customizable using the following properties:
71 |
72 | ``` swift
73 | // Default is 48.0 for each.
74 | var minWidth: CGFloat = 48.0
75 | var minHeight: CGFloat = 48.0
76 |
77 | // Defaults to YES. Disables the user from dragging the view outside the parent view's bounds.
78 | var isPreventsPositionOutsideSuperview: Bool = true
79 | ```
80 |
81 | For an example of how to use RKUserResizableView, please see the included example project.
82 |
83 | ## Acknowledgments
84 |
85 | * Hat tip to Stephen Poletto [spoletto](https://github.com/spoletto)
86 |
87 |
--------------------------------------------------------------------------------
/RKUserResizableViewDemo/RKUserResizableViewDemo.xcodeproj/xcuserdata/user.xcuserdatad/xcschemes/RKUserResizableViewDemo.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/RKUserResizableViewDemo/RKUserResizableViewDemo.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | CEE4F6011F5675B300C12FF8 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE4F6001F5675B300C12FF8 /* AppDelegate.swift */; };
11 | CEE4F6031F5675B300C12FF8 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE4F6021F5675B300C12FF8 /* ViewController.swift */; };
12 | CEE4F6061F5675B300C12FF8 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CEE4F6041F5675B300C12FF8 /* Main.storyboard */; };
13 | CEE4F6081F5675B300C12FF8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CEE4F6071F5675B300C12FF8 /* Assets.xcassets */; };
14 | CEE4F60B1F5675B300C12FF8 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CEE4F6091F5675B300C12FF8 /* LaunchScreen.storyboard */; };
15 | CEE4F6131F5675E800C12FF8 /* RKUserResizableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE4F6121F5675E800C12FF8 /* RKUserResizableView.swift */; };
16 | /* End PBXBuildFile section */
17 |
18 | /* Begin PBXFileReference section */
19 | CEE4F5FD1F5675B300C12FF8 /* RKUserResizableViewDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RKUserResizableViewDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
20 | CEE4F6001F5675B300C12FF8 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
21 | CEE4F6021F5675B300C12FF8 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
22 | CEE4F6051F5675B300C12FF8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
23 | CEE4F6071F5675B300C12FF8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
24 | CEE4F60A1F5675B300C12FF8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
25 | CEE4F60C1F5675B300C12FF8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
26 | CEE4F6121F5675E800C12FF8 /* RKUserResizableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RKUserResizableView.swift; sourceTree = ""; };
27 | /* End PBXFileReference section */
28 |
29 | /* Begin PBXFrameworksBuildPhase section */
30 | CEE4F5FA1F5675B300C12FF8 /* Frameworks */ = {
31 | isa = PBXFrameworksBuildPhase;
32 | buildActionMask = 2147483647;
33 | files = (
34 | );
35 | runOnlyForDeploymentPostprocessing = 0;
36 | };
37 | /* End PBXFrameworksBuildPhase section */
38 |
39 | /* Begin PBXGroup section */
40 | CEE4F5F41F5675B300C12FF8 = {
41 | isa = PBXGroup;
42 | children = (
43 | CEE4F5FF1F5675B300C12FF8 /* RKUserResizableViewDemo */,
44 | CEE4F5FE1F5675B300C12FF8 /* Products */,
45 | );
46 | sourceTree = "";
47 | };
48 | CEE4F5FE1F5675B300C12FF8 /* Products */ = {
49 | isa = PBXGroup;
50 | children = (
51 | CEE4F5FD1F5675B300C12FF8 /* RKUserResizableViewDemo.app */,
52 | );
53 | name = Products;
54 | sourceTree = "";
55 | };
56 | CEE4F5FF1F5675B300C12FF8 /* RKUserResizableViewDemo */ = {
57 | isa = PBXGroup;
58 | children = (
59 | CEE4F6001F5675B300C12FF8 /* AppDelegate.swift */,
60 | CEE4F6021F5675B300C12FF8 /* ViewController.swift */,
61 | CEE4F6041F5675B300C12FF8 /* Main.storyboard */,
62 | CEE4F6071F5675B300C12FF8 /* Assets.xcassets */,
63 | CEE4F6091F5675B300C12FF8 /* LaunchScreen.storyboard */,
64 | CEE4F60C1F5675B300C12FF8 /* Info.plist */,
65 | CEE4F6121F5675E800C12FF8 /* RKUserResizableView.swift */,
66 | );
67 | path = RKUserResizableViewDemo;
68 | sourceTree = "";
69 | };
70 | /* End PBXGroup section */
71 |
72 | /* Begin PBXNativeTarget section */
73 | CEE4F5FC1F5675B300C12FF8 /* RKUserResizableViewDemo */ = {
74 | isa = PBXNativeTarget;
75 | buildConfigurationList = CEE4F60F1F5675B300C12FF8 /* Build configuration list for PBXNativeTarget "RKUserResizableViewDemo" */;
76 | buildPhases = (
77 | CEE4F5F91F5675B300C12FF8 /* Sources */,
78 | CEE4F5FA1F5675B300C12FF8 /* Frameworks */,
79 | CEE4F5FB1F5675B300C12FF8 /* Resources */,
80 | );
81 | buildRules = (
82 | );
83 | dependencies = (
84 | );
85 | name = RKUserResizableViewDemo;
86 | productName = RKUserResizableViewDemo;
87 | productReference = CEE4F5FD1F5675B300C12FF8 /* RKUserResizableViewDemo.app */;
88 | productType = "com.apple.product-type.application";
89 | };
90 | /* End PBXNativeTarget section */
91 |
92 | /* Begin PBXProject section */
93 | CEE4F5F51F5675B300C12FF8 /* Project object */ = {
94 | isa = PBXProject;
95 | attributes = {
96 | LastSwiftUpdateCheck = 0830;
97 | LastUpgradeCheck = 0830;
98 | ORGANIZATIONNAME = rk;
99 | TargetAttributes = {
100 | CEE4F5FC1F5675B300C12FF8 = {
101 | CreatedOnToolsVersion = 8.3.2;
102 | DevelopmentTeam = 952ZU65CH6;
103 | ProvisioningStyle = Automatic;
104 | };
105 | };
106 | };
107 | buildConfigurationList = CEE4F5F81F5675B300C12FF8 /* Build configuration list for PBXProject "RKUserResizableViewDemo" */;
108 | compatibilityVersion = "Xcode 3.2";
109 | developmentRegion = English;
110 | hasScannedForEncodings = 0;
111 | knownRegions = (
112 | en,
113 | Base,
114 | );
115 | mainGroup = CEE4F5F41F5675B300C12FF8;
116 | productRefGroup = CEE4F5FE1F5675B300C12FF8 /* Products */;
117 | projectDirPath = "";
118 | projectRoot = "";
119 | targets = (
120 | CEE4F5FC1F5675B300C12FF8 /* RKUserResizableViewDemo */,
121 | );
122 | };
123 | /* End PBXProject section */
124 |
125 | /* Begin PBXResourcesBuildPhase section */
126 | CEE4F5FB1F5675B300C12FF8 /* Resources */ = {
127 | isa = PBXResourcesBuildPhase;
128 | buildActionMask = 2147483647;
129 | files = (
130 | CEE4F60B1F5675B300C12FF8 /* LaunchScreen.storyboard in Resources */,
131 | CEE4F6081F5675B300C12FF8 /* Assets.xcassets in Resources */,
132 | CEE4F6061F5675B300C12FF8 /* Main.storyboard in Resources */,
133 | );
134 | runOnlyForDeploymentPostprocessing = 0;
135 | };
136 | /* End PBXResourcesBuildPhase section */
137 |
138 | /* Begin PBXSourcesBuildPhase section */
139 | CEE4F5F91F5675B300C12FF8 /* Sources */ = {
140 | isa = PBXSourcesBuildPhase;
141 | buildActionMask = 2147483647;
142 | files = (
143 | CEE4F6031F5675B300C12FF8 /* ViewController.swift in Sources */,
144 | CEE4F6131F5675E800C12FF8 /* RKUserResizableView.swift in Sources */,
145 | CEE4F6011F5675B300C12FF8 /* AppDelegate.swift in Sources */,
146 | );
147 | runOnlyForDeploymentPostprocessing = 0;
148 | };
149 | /* End PBXSourcesBuildPhase section */
150 |
151 | /* Begin PBXVariantGroup section */
152 | CEE4F6041F5675B300C12FF8 /* Main.storyboard */ = {
153 | isa = PBXVariantGroup;
154 | children = (
155 | CEE4F6051F5675B300C12FF8 /* Base */,
156 | );
157 | name = Main.storyboard;
158 | sourceTree = "";
159 | };
160 | CEE4F6091F5675B300C12FF8 /* LaunchScreen.storyboard */ = {
161 | isa = PBXVariantGroup;
162 | children = (
163 | CEE4F60A1F5675B300C12FF8 /* Base */,
164 | );
165 | name = LaunchScreen.storyboard;
166 | sourceTree = "";
167 | };
168 | /* End PBXVariantGroup section */
169 |
170 | /* Begin XCBuildConfiguration section */
171 | CEE4F60D1F5675B300C12FF8 /* Debug */ = {
172 | isa = XCBuildConfiguration;
173 | buildSettings = {
174 | ALWAYS_SEARCH_USER_PATHS = NO;
175 | CLANG_ANALYZER_NONNULL = YES;
176 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
177 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
178 | CLANG_CXX_LIBRARY = "libc++";
179 | CLANG_ENABLE_MODULES = YES;
180 | CLANG_ENABLE_OBJC_ARC = YES;
181 | CLANG_WARN_BOOL_CONVERSION = YES;
182 | CLANG_WARN_CONSTANT_CONVERSION = YES;
183 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
184 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
185 | CLANG_WARN_EMPTY_BODY = YES;
186 | CLANG_WARN_ENUM_CONVERSION = YES;
187 | CLANG_WARN_INFINITE_RECURSION = YES;
188 | CLANG_WARN_INT_CONVERSION = YES;
189 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
190 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
191 | CLANG_WARN_UNREACHABLE_CODE = YES;
192 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
193 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
194 | COPY_PHASE_STRIP = NO;
195 | DEBUG_INFORMATION_FORMAT = dwarf;
196 | ENABLE_STRICT_OBJC_MSGSEND = YES;
197 | ENABLE_TESTABILITY = YES;
198 | GCC_C_LANGUAGE_STANDARD = gnu99;
199 | GCC_DYNAMIC_NO_PIC = NO;
200 | GCC_NO_COMMON_BLOCKS = YES;
201 | GCC_OPTIMIZATION_LEVEL = 0;
202 | GCC_PREPROCESSOR_DEFINITIONS = (
203 | "DEBUG=1",
204 | "$(inherited)",
205 | );
206 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
207 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
208 | GCC_WARN_UNDECLARED_SELECTOR = YES;
209 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
210 | GCC_WARN_UNUSED_FUNCTION = YES;
211 | GCC_WARN_UNUSED_VARIABLE = YES;
212 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
213 | MTL_ENABLE_DEBUG_INFO = YES;
214 | ONLY_ACTIVE_ARCH = YES;
215 | SDKROOT = iphoneos;
216 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
217 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
218 | TARGETED_DEVICE_FAMILY = 2;
219 | };
220 | name = Debug;
221 | };
222 | CEE4F60E1F5675B300C12FF8 /* Release */ = {
223 | isa = XCBuildConfiguration;
224 | buildSettings = {
225 | ALWAYS_SEARCH_USER_PATHS = NO;
226 | CLANG_ANALYZER_NONNULL = YES;
227 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
228 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
229 | CLANG_CXX_LIBRARY = "libc++";
230 | CLANG_ENABLE_MODULES = YES;
231 | CLANG_ENABLE_OBJC_ARC = YES;
232 | CLANG_WARN_BOOL_CONVERSION = YES;
233 | CLANG_WARN_CONSTANT_CONVERSION = YES;
234 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
235 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
236 | CLANG_WARN_EMPTY_BODY = YES;
237 | CLANG_WARN_ENUM_CONVERSION = YES;
238 | CLANG_WARN_INFINITE_RECURSION = YES;
239 | CLANG_WARN_INT_CONVERSION = YES;
240 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
241 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
242 | CLANG_WARN_UNREACHABLE_CODE = YES;
243 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
244 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
245 | COPY_PHASE_STRIP = NO;
246 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
247 | ENABLE_NS_ASSERTIONS = NO;
248 | ENABLE_STRICT_OBJC_MSGSEND = YES;
249 | GCC_C_LANGUAGE_STANDARD = gnu99;
250 | GCC_NO_COMMON_BLOCKS = YES;
251 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
252 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
253 | GCC_WARN_UNDECLARED_SELECTOR = YES;
254 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
255 | GCC_WARN_UNUSED_FUNCTION = YES;
256 | GCC_WARN_UNUSED_VARIABLE = YES;
257 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
258 | MTL_ENABLE_DEBUG_INFO = NO;
259 | SDKROOT = iphoneos;
260 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
261 | TARGETED_DEVICE_FAMILY = 2;
262 | VALIDATE_PRODUCT = YES;
263 | };
264 | name = Release;
265 | };
266 | CEE4F6101F5675B300C12FF8 /* Debug */ = {
267 | isa = XCBuildConfiguration;
268 | buildSettings = {
269 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
270 | DEVELOPMENT_TEAM = 952ZU65CH6;
271 | INFOPLIST_FILE = RKUserResizableViewDemo/Info.plist;
272 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
273 | PRODUCT_BUNDLE_IDENTIFIER = com.rajatjain4061.RKUserResizableViewDemo;
274 | PRODUCT_NAME = "$(TARGET_NAME)";
275 | SWIFT_VERSION = 3.0;
276 | };
277 | name = Debug;
278 | };
279 | CEE4F6111F5675B300C12FF8 /* Release */ = {
280 | isa = XCBuildConfiguration;
281 | buildSettings = {
282 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
283 | DEVELOPMENT_TEAM = 952ZU65CH6;
284 | INFOPLIST_FILE = RKUserResizableViewDemo/Info.plist;
285 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
286 | PRODUCT_BUNDLE_IDENTIFIER = com.rajatjain4061.RKUserResizableViewDemo;
287 | PRODUCT_NAME = "$(TARGET_NAME)";
288 | SWIFT_VERSION = 3.0;
289 | };
290 | name = Release;
291 | };
292 | /* End XCBuildConfiguration section */
293 |
294 | /* Begin XCConfigurationList section */
295 | CEE4F5F81F5675B300C12FF8 /* Build configuration list for PBXProject "RKUserResizableViewDemo" */ = {
296 | isa = XCConfigurationList;
297 | buildConfigurations = (
298 | CEE4F60D1F5675B300C12FF8 /* Debug */,
299 | CEE4F60E1F5675B300C12FF8 /* Release */,
300 | );
301 | defaultConfigurationIsVisible = 0;
302 | defaultConfigurationName = Release;
303 | };
304 | CEE4F60F1F5675B300C12FF8 /* Build configuration list for PBXNativeTarget "RKUserResizableViewDemo" */ = {
305 | isa = XCConfigurationList;
306 | buildConfigurations = (
307 | CEE4F6101F5675B300C12FF8 /* Debug */,
308 | CEE4F6111F5675B300C12FF8 /* Release */,
309 | );
310 | defaultConfigurationIsVisible = 0;
311 | };
312 | /* End XCConfigurationList section */
313 | };
314 | rootObject = CEE4F5F51F5675B300C12FF8 /* Project object */;
315 | }
316 |
--------------------------------------------------------------------------------
/RKUserResizableView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RKUserResizableView.swift
3 | // RKUserResizableViewDemo
4 | //
5 | // Created by Rajat Jain on 28/08/17.
6 | // Converted to Swift from https://github.com/spoletto/SPUserResizableView
7 | //
8 |
9 |
10 | import UIKit
11 |
12 | struct RKUserResizableViewAnchorPoint {
13 | var adjustsX: CGFloat = 0.0
14 | var adjustsY: CGFloat = 0.0
15 | var adjustsH: CGFloat = 0.0
16 | var adjustsW: CGFloat = 0.0
17 | }
18 |
19 | struct RKUserResizableViewAnchorPointPair {
20 | var point: CGPoint = CGPoint.zero
21 | var anchorPoint: RKUserResizableViewAnchorPoint = RKUserResizableViewAnchorPoint()
22 | }
23 |
24 | protocol RKUserResizableViewDelegate: NSObjectProtocol {
25 | // Called when the resizable view receives touchesBegan: and activates the editing handles.
26 | func userResizableViewDidBeginEditing(_ userResizableView: RKUserResizableView)
27 |
28 | // Called when the resizable view receives touchesEnded: or touchesCancelled:
29 | func userResizableViewDidEndEditing(_ userResizableView: RKUserResizableView)
30 | }
31 |
32 |
33 | class RKUserResizableView:UIView {
34 |
35 | var borderView: GripViewBorderView?
36 |
37 | // Will be retained as a subview.
38 | var _contentView: UIView?
39 |
40 | var contentView:UIView? {
41 | set (newValue) {
42 | newValue?.removeFromSuperview()
43 | newValue?.frame = bounds.insetBy(dx: RKUserResizableViewGlobalInset + RKUserResizableViewInteractiveBorderSize / 2, dy: RKUserResizableViewGlobalInset + RKUserResizableViewInteractiveBorderSize / 2)
44 | addSubview(newValue!)
45 | // Ensure the border view is always on top by removing it and adding it to the end of the subview list.
46 | borderView?.removeFromSuperview()
47 | addSubview(borderView!)
48 | self._contentView = newValue
49 | }
50 | get {
51 | return self._contentView
52 | }
53 | }
54 |
55 | var touchStart = CGPoint.zero
56 |
57 | // Default is 48.0 for each.
58 | var minWidth: CGFloat = 48.0
59 | var minHeight: CGFloat = 48.0
60 |
61 | // Used to determine which components of the bounds we'll be modifying, based upon where the user's touch started.
62 | var anchorPoint = RKUserResizableViewAnchorPoint()
63 |
64 | weak var delegate: RKUserResizableViewDelegate?
65 |
66 | /* Let's inset everything that's drawn (the handles and the content view)
67 | so that users can trigger a resize from a few pixels outside of
68 | what they actually see as the bounding box. */
69 | let RKUserResizableViewGlobalInset:CGFloat = 5.0
70 | let RKUserResizableViewDefaultMinWidth:CGFloat = 48.0
71 | let RKUserResizableViewDefaultMinHeight:CGFloat = 48.0
72 | let RKUserResizableViewInteractiveBorderSize:CGFloat = 10.0
73 |
74 | // Defaults to YES. Disables the user from dragging the view outside the parent view's bounds.
75 |
76 | var isPreventsPositionOutsideSuperview: Bool = true
77 |
78 | private var RKUserResizableViewNoResizeAnchorPoint = RKUserResizableViewAnchorPoint(adjustsX: 0.0, adjustsY: 0.0, adjustsH: 0.0, adjustsW: 0.0)
79 | private var RKUserResizableViewUpperLeftAnchorPoint = RKUserResizableViewAnchorPoint(adjustsX: 1.0, adjustsY: 1.0, adjustsH: -1.0, adjustsW: 1.0)
80 | private var RKUserResizableViewMiddleLeftAnchorPoint = RKUserResizableViewAnchorPoint(adjustsX: 1.0, adjustsY: 0.0, adjustsH: 0.0, adjustsW: 1.0)
81 | private var RKUserResizableViewLowerLeftAnchorPoint = RKUserResizableViewAnchorPoint(adjustsX: 1.0, adjustsY: 0.0, adjustsH: 1.0, adjustsW: 1.0)
82 | private var RKUserResizableViewUpperMiddleAnchorPoint = RKUserResizableViewAnchorPoint(adjustsX: 0.0, adjustsY: 1.0, adjustsH: -1.0, adjustsW: 0.0)
83 | private var RKUserResizableViewUpperRightAnchorPoint = RKUserResizableViewAnchorPoint(adjustsX: 0.0, adjustsY: 1.0, adjustsH: -1.0, adjustsW: -1.0)
84 | private var RKUserResizableViewMiddleRightAnchorPoint = RKUserResizableViewAnchorPoint(adjustsX: 0.0, adjustsY: 0.0, adjustsH: 0.0, adjustsW: -1.0)
85 | private var RKUserResizableViewLowerRightAnchorPoint = RKUserResizableViewAnchorPoint(adjustsX: 0.0, adjustsY: 0.0, adjustsH: 1.0, adjustsW: -1.0)
86 | private var RKUserResizableViewLowerMiddleAnchorPoint = RKUserResizableViewAnchorPoint(adjustsX: 0.0, adjustsY: 0.0, adjustsH: 1.0, adjustsW: 0.0)
87 |
88 | func setupDefaultAttributes() {
89 | borderView = GripViewBorderView(frame: bounds.insetBy(dx: CGFloat(RKUserResizableViewGlobalInset), dy: CGFloat(RKUserResizableViewGlobalInset)))
90 | borderView?.isHidden = true
91 | self.addSubview(borderView!)
92 | minWidth = RKUserResizableViewDefaultMinWidth
93 | minHeight = RKUserResizableViewDefaultMinHeight
94 | isPreventsPositionOutsideSuperview = true
95 | }
96 |
97 | override init(frame: CGRect) {
98 | super.init(frame: frame)
99 | setupDefaultAttributes()
100 | }
101 |
102 | required init?(coder aDecoder: NSCoder) {
103 | super.init(coder: aDecoder)
104 | setupDefaultAttributes()
105 | }
106 |
107 | func setViewFrame(_ newFrame: CGRect) {
108 | self.frame = newFrame
109 | contentView?.frame = bounds.insetBy(dx: RKUserResizableViewGlobalInset + RKUserResizableViewInteractiveBorderSize / 2, dy: RKUserResizableViewGlobalInset + RKUserResizableViewInteractiveBorderSize / 2)
110 | borderView?.frame = bounds.insetBy(dx: RKUserResizableViewGlobalInset, dy: RKUserResizableViewGlobalInset)
111 | borderView?.setNeedsDisplay()
112 |
113 | }
114 |
115 | private func distanceBetweenTwoPoints(point1: CGPoint, point2: CGPoint) -> CGFloat {
116 | let dx: CGFloat = point2.x - point1.x
117 | let dy: CGFloat = point2.y - point1.y
118 | return sqrt(dx * dx + dy * dy)
119 | }
120 |
121 | func anchorPoint(forTouchLocation touchPoint: CGPoint) -> RKUserResizableViewAnchorPoint {
122 | // (1) Calculate the positions of each of the anchor points.
123 | let upperLeft = RKUserResizableViewAnchorPointPair(point: CGPoint(x: 0.0, y: 0.0), anchorPoint: RKUserResizableViewUpperLeftAnchorPoint)
124 |
125 | let upperMiddle = RKUserResizableViewAnchorPointPair(point: CGPoint(x: bounds.size.width / 2, y: 0.0), anchorPoint: RKUserResizableViewUpperMiddleAnchorPoint)
126 |
127 | let upperRight = RKUserResizableViewAnchorPointPair(point: CGPoint(x: bounds.size.width, y: 0.0), anchorPoint: RKUserResizableViewUpperRightAnchorPoint)
128 |
129 | let middleRight = RKUserResizableViewAnchorPointPair(point: CGPoint(x: bounds.size.width, y: bounds.size.height / 2), anchorPoint: RKUserResizableViewMiddleRightAnchorPoint)
130 |
131 | let lowerRight = RKUserResizableViewAnchorPointPair(point: CGPoint(x: bounds.size.width, y: bounds.size.height), anchorPoint: RKUserResizableViewLowerRightAnchorPoint)
132 |
133 | let lowerMiddle = RKUserResizableViewAnchorPointPair(point: CGPoint(x: bounds.size.width / 2, y: bounds.size.height), anchorPoint: RKUserResizableViewLowerMiddleAnchorPoint)
134 |
135 | let lowerLeft = RKUserResizableViewAnchorPointPair(point: CGPoint(x: 0, y: bounds.size.height), anchorPoint: RKUserResizableViewLowerLeftAnchorPoint)
136 |
137 | let middleLeft = RKUserResizableViewAnchorPointPair(point: CGPoint(x: 0, y: bounds.size.height / 2), anchorPoint: RKUserResizableViewMiddleLeftAnchorPoint)
138 |
139 | let centerPoint = RKUserResizableViewAnchorPointPair(point: CGPoint(x: bounds.size.width / 2, y: bounds.size.height / 2), anchorPoint: RKUserResizableViewNoResizeAnchorPoint)
140 |
141 | // (2) Iterate over each of the anchor points and find the one closest to the user's touch.
142 | let allPoints: [RKUserResizableViewAnchorPointPair] = [upperLeft, upperRight, lowerRight, lowerLeft, upperMiddle, lowerMiddle, middleLeft, middleRight, centerPoint]
143 | var smallestDistance: CGFloat = CGFloat(MAXFLOAT)
144 | var closestPoint: RKUserResizableViewAnchorPointPair = centerPoint
145 | for i in 0..<9 {
146 | let distance: CGFloat = distanceBetweenTwoPoints(point1: touchPoint, point2: allPoints[i].point)
147 | if distance < smallestDistance {
148 | closestPoint = allPoints[i]
149 | smallestDistance = distance
150 | }
151 | }
152 | return closestPoint.anchorPoint
153 | }
154 |
155 | func isResizing() -> Bool {
156 | return (anchorPoint.adjustsH != 0 || anchorPoint.adjustsW != 0 || anchorPoint.adjustsX != 0 || anchorPoint.adjustsY != 0)
157 | }
158 |
159 | override func touchesBegan(_ touches: Set, with event: UIEvent?) {
160 | super.touchesBegan(touches, with: event)
161 | // Notify the delegate we've begun our editing session.
162 | // if delegate?.responds(to: #selector(self.userResizableViewDidBeginEditing)) {
163 | delegate?.userResizableViewDidBeginEditing(self)
164 | // }
165 | borderView?.isHidden = false
166 |
167 | if let touch = touches.first {
168 | anchorPoint = anchorPoint(forTouchLocation: touch.location(in: self))
169 | print(anchorPoint)
170 | // When resizing, all calculations are done in the superview's coordinate space.
171 | touchStart = touch.location(in: superview)
172 | if !isResizing() {
173 | // When translating, all calculations are done in the view's coordinate space.
174 | touchStart = touch.location(in: self)
175 | }
176 | }
177 | }
178 |
179 | override func touchesEnded(_ touches: Set, with event: UIEvent?) {
180 | // Notify the delegate we've ended our editing session.
181 | delegate?.userResizableViewDidEndEditing(self)
182 | }
183 |
184 | override func touchesCancelled(_ touches: Set, with event: UIEvent?) {
185 | // Notify the delegate we've ended our editing session.
186 | delegate?.userResizableViewDidEndEditing(self)
187 | }
188 |
189 | func showEditingHandles() {
190 | borderView?.isHidden = false
191 | }
192 |
193 | func hideEditingHandles() {
194 | borderView?.isHidden = true
195 | }
196 |
197 | func resize(usingTouchLocation touchPoint: CGPoint) {
198 | // (1) Update the touch point if we're outside the superview.
199 | /* disabled by rajat jain on 2017-08-28
200 | if isPreventsPositionOutsideSuperview {
201 | let border: CGFloat = RKUserResizableViewGlobalInset + RKUserResizableViewInteractiveBorderSize / 2
202 | if touchPoint.x < border {
203 | touchPoint.x = border
204 | }
205 | if touchPoint.x > superview?.bounds.size.width - border {
206 | touchPoint.x = superview?.bounds.size.width - border
207 | }
208 | if touchPoint.y < border {
209 | touchPoint.y = border
210 | }
211 | if touchPoint.y > superview?.bounds.size.height - border {
212 | touchPoint.y = superview?.bounds.size.height - border
213 | }
214 | }
215 | */
216 | // (2) Calculate the deltas using the current anchor point.
217 | var deltaW: CGFloat = anchorPoint.adjustsW * (touchStart.x - touchPoint.x)
218 | let deltaX: CGFloat = anchorPoint.adjustsX * (-1.0 * deltaW)
219 | var deltaH: CGFloat = anchorPoint.adjustsH * (touchPoint.y - touchStart.y)
220 | let deltaY: CGFloat = anchorPoint.adjustsY * (-1.0 * deltaH)
221 | // (3) Calculate the new frame.
222 | var newX: CGFloat = frame.origin.x + deltaX
223 | var newY: CGFloat = frame.origin.y + deltaY
224 | var newWidth: CGFloat = frame.size.width + deltaW
225 | var newHeight: CGFloat = frame.size.height + deltaH
226 | // (4) If the new frame is too small, cancel the changes.
227 | if newWidth < minWidth {
228 | newWidth = frame.size.width
229 | newX = frame.origin.x
230 | }
231 | if newHeight < minHeight {
232 | newHeight = frame.size.height
233 | newY = frame.origin.y
234 | }
235 |
236 | // (5) Ensure the resize won't cause the view to move offscreen.
237 | if isPreventsPositionOutsideSuperview {
238 | if let superView = self.superview {
239 | if newX < superView.bounds.origin.x {
240 | // Calculate how much to grow the width by such that the new X coordintae will align with the superview.
241 | deltaW = self.frame.origin.x - superView.bounds.origin.x
242 | newWidth = self.frame.size.width + deltaW
243 | newX = superView.bounds.origin.x
244 | }
245 |
246 | if newX + newWidth > superView.bounds.origin.x + superView.bounds.size.width {
247 | newWidth = superView.bounds.size.width - newX
248 | }
249 |
250 | if newY < superView.bounds.origin.y {
251 | // Calculate how much to grow the height by such that the new Y coordintae will align with the superview.
252 | deltaH = self.frame.origin.y - superView.bounds.origin.y
253 | newHeight = self.frame.size.height + deltaH
254 | newY = superView.bounds.origin.y
255 | }
256 |
257 | if newY + newHeight > superView.bounds.origin.y + superView.bounds.size.height {
258 | newHeight = superView.bounds.size.height - newY
259 | }
260 |
261 | }
262 | }
263 |
264 | self.setViewFrame(CGRect(x: newX, y: newY, width: newWidth, height: newHeight))
265 |
266 | touchStart = touchPoint
267 | }
268 |
269 | func translate(usingTouchLocation touchPoint: CGPoint) {
270 | var newCenter = CGPoint(x: center.x + touchPoint.x - touchStart.x, y: center.y + touchPoint.y - touchStart.y)
271 | if isPreventsPositionOutsideSuperview {
272 | if let superView = self.superview {
273 |
274 | // Ensure the translation won't cause the view to move offscreen.
275 | let midPointX: CGFloat = bounds.midX
276 | if newCenter.x > superView.bounds.size.width - midPointX {
277 | newCenter.x = superView.bounds.size.width - midPointX
278 | }
279 | if newCenter.x < midPointX {
280 | newCenter.x = midPointX
281 | }
282 | let midPointY: CGFloat = bounds.midY
283 | if newCenter.y > superView.bounds.size.height - midPointY {
284 | newCenter.y = superView.bounds.size.height - midPointY
285 | }
286 | if newCenter.y < midPointY {
287 | newCenter.y = midPointY
288 | }
289 | }
290 | }
291 | center = newCenter
292 | }
293 |
294 | override func touchesMoved(_ touches: Set, with event: UIEvent?) {
295 | if isResizing() {
296 | if let superView = self.superview {
297 | resize(usingTouchLocation: (touches.first?.location(in: superView))!)
298 | }
299 | }
300 | else {
301 | translate(usingTouchLocation: (touches.first?.location(in: self))!)
302 | }
303 | }
304 |
305 | deinit {
306 | contentView?.removeFromSuperview()
307 | }
308 | }
309 |
310 |
311 | class GripViewBorderView: UIView {
312 |
313 | let RKUserResizableViewGlobalInset:CGFloat = 5.0
314 | let RKUserResizableViewDefaultMinWidth:CGFloat = 48.0
315 | let RKUserResizableViewDefaultMinHeight:CGFloat = 48.0
316 | let RKUserResizableViewInteractiveBorderSize:CGFloat = 10.0
317 |
318 | weak var delegate: RKUserResizableViewDelegate?
319 | // Will be retained as a subview.
320 | var contentView: UIView?
321 | // Default is 48.0 for each.
322 | var minWidth: CGFloat = 0.0
323 | var minHeight: CGFloat = 0.0
324 | // Defaults to YES. Disables the user from dragging the view outside the parent view's bounds.
325 | var isPreventsPositionOutsideSuperview: Bool = false
326 |
327 | var borderView: GripViewBorderView?
328 | var touchStart = CGPoint.zero
329 | // Used to determine which components of the bounds we'll be modifying, based upon where the user's touch started.
330 | var anchorPoint = RKUserResizableViewAnchorPoint()
331 |
332 | func hideEditingHandles() {
333 | }
334 |
335 | func showEditingHandles() {
336 | }
337 |
338 | override init(frame: CGRect) {
339 | // Clear background to ensure the content view shows through.
340 | super.init(frame: frame)
341 | self.backgroundColor = .clear
342 | }
343 |
344 | required init?(coder aDecoder: NSCoder) {
345 | fatalError("init(coder:) has not been implemented")
346 | }
347 |
348 | override func draw(_ rect: CGRect) {
349 | let context: CGContext? = UIGraphicsGetCurrentContext()
350 | context?.saveGState()
351 |
352 | // (1) Draw the bounding box.
353 | context?.setLineWidth(1.0)
354 | context?.setStrokeColor(UIColor.blue.cgColor)
355 | context?.addRect(bounds.insetBy(dx: RKUserResizableViewInteractiveBorderSize / 2, dy: RKUserResizableViewInteractiveBorderSize / 2))
356 | context?.strokePath()
357 |
358 | // (2) Calculate the bounding boxes for each of the anchor points.
359 | let upperLeft = CGRect(x: 0.0, y: 0.0, width: RKUserResizableViewInteractiveBorderSize, height: RKUserResizableViewInteractiveBorderSize)
360 | let upperRight = CGRect(x: bounds.size.width - RKUserResizableViewInteractiveBorderSize, y: 0.0, width: RKUserResizableViewInteractiveBorderSize, height: RKUserResizableViewInteractiveBorderSize)
361 | let lowerRight = CGRect(x: bounds.size.width - RKUserResizableViewInteractiveBorderSize, y: bounds.size.height - RKUserResizableViewInteractiveBorderSize, width: RKUserResizableViewInteractiveBorderSize, height: RKUserResizableViewInteractiveBorderSize)
362 | let lowerLeft = CGRect(x: 0.0, y: bounds.size.height - RKUserResizableViewInteractiveBorderSize, width: RKUserResizableViewInteractiveBorderSize, height: RKUserResizableViewInteractiveBorderSize)
363 | let upperMiddle = CGRect(x: (bounds.size.width - RKUserResizableViewInteractiveBorderSize) / 2, y: 0.0, width: RKUserResizableViewInteractiveBorderSize, height: RKUserResizableViewInteractiveBorderSize)
364 | let lowerMiddle = CGRect(x: (bounds.size.width - RKUserResizableViewInteractiveBorderSize) / 2, y: bounds.size.height - RKUserResizableViewInteractiveBorderSize, width: RKUserResizableViewInteractiveBorderSize, height: RKUserResizableViewInteractiveBorderSize)
365 | let middleLeft = CGRect(x: 0.0, y: (bounds.size.height - RKUserResizableViewInteractiveBorderSize) / 2, width: RKUserResizableViewInteractiveBorderSize, height: RKUserResizableViewInteractiveBorderSize)
366 | let middleRight = CGRect(x: bounds.size.width - RKUserResizableViewInteractiveBorderSize, y: (bounds.size.height - RKUserResizableViewInteractiveBorderSize) / 2, width: RKUserResizableViewInteractiveBorderSize, height: RKUserResizableViewInteractiveBorderSize)
367 |
368 | // (3) Create the gradient to paint the anchor points.
369 | let colors: [CGFloat] = [0.4, 0.8, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0]
370 | let baseSpace: CGColorSpace? = CGColorSpaceCreateDeviceRGB()
371 | let gradient: CGGradient = CGGradient(colorSpace: baseSpace!, colorComponents: colors, locations: nil, count: 2)!
372 |
373 | // (4) Set up the stroke for drawing the border of each of the anchor points.
374 | context?.setLineWidth(2)
375 | context!.setShadow(offset: CGSize(width: 0.5, height: 0.5), blur: 1)
376 | context?.setStrokeColor(UIColor.white.cgColor)
377 |
378 | // (5) Fill each anchor point using the gradient, then stroke the border.
379 | let allPoints: [CGRect] = [upperLeft, upperRight, lowerRight, lowerLeft, upperMiddle, lowerMiddle, middleLeft, middleRight]
380 | for i in 0..<8 {
381 | let currPoint: CGRect = allPoints[i]
382 | context?.saveGState()
383 | //context?.addEllipse(in: currPoint)
384 | let currentPoint = CGRect(x: currPoint.origin.x, y: currPoint.origin.y, width: 10, height: 10);
385 | context?.addEllipse(in: currentPoint)
386 | context?.clip()
387 | let startPoint = CGPoint(x: currentPoint.midX, y: currentPoint.minY)
388 | let endPoint = CGPoint(x: currentPoint.midX, y: currentPoint.maxY)
389 | context?.drawLinearGradient(gradient, start: startPoint, end: endPoint, options: [])
390 | context?.restoreGState()
391 | context?.strokeEllipse(in: currentPoint.insetBy(dx: 1, dy: 1))
392 | }
393 | context?.restoreGState()
394 | }
395 | }
396 |
397 |
--------------------------------------------------------------------------------
/RKUserResizableViewDemo/RKUserResizableViewDemo/RKUserResizableView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RKUserResizableView.swift
3 | // RKUserResizableViewDemo
4 | //
5 | // Created by Rajat Jain on 28/08/17.
6 | // Converted to Swift from https://github.com/spoletto/SPUserResizableView
7 | //
8 |
9 |
10 | import UIKit
11 |
12 | struct RKUserResizableViewAnchorPoint {
13 | var adjustsX: CGFloat = 0.0
14 | var adjustsY: CGFloat = 0.0
15 | var adjustsH: CGFloat = 0.0
16 | var adjustsW: CGFloat = 0.0
17 | }
18 |
19 | struct RKUserResizableViewAnchorPointPair {
20 | var point: CGPoint = CGPoint.zero
21 | var anchorPoint: RKUserResizableViewAnchorPoint = RKUserResizableViewAnchorPoint()
22 | }
23 |
24 | protocol RKUserResizableViewDelegate: NSObjectProtocol {
25 | // Called when the resizable view receives touchesBegan: and activates the editing handles.
26 | func userResizableViewDidBeginEditing(_ userResizableView: RKUserResizableView)
27 |
28 | // Called when the resizable view receives touchesEnded: or touchesCancelled:
29 | func userResizableViewDidEndEditing(_ userResizableView: RKUserResizableView)
30 | }
31 |
32 |
33 | class RKUserResizableView:UIView {
34 |
35 | var borderView: GripViewBorderView?
36 |
37 | // Will be retained as a subview.
38 | var _contentView: UIView?
39 |
40 | var contentView:UIView? {
41 | set (newValue) {
42 | newValue?.removeFromSuperview()
43 | newValue?.frame = bounds.insetBy(dx: RKUserResizableViewGlobalInset + RKUserResizableViewInteractiveBorderSize / 2, dy: RKUserResizableViewGlobalInset + RKUserResizableViewInteractiveBorderSize / 2)
44 | addSubview(newValue!)
45 | // Ensure the border view is always on top by removing it and adding it to the end of the subview list.
46 | borderView?.removeFromSuperview()
47 | addSubview(borderView!)
48 | self._contentView = newValue
49 | }
50 | get {
51 | return self._contentView
52 | }
53 | }
54 |
55 | var touchStart = CGPoint.zero
56 |
57 | // Default is 48.0 for each.
58 | var minWidth: CGFloat = 48.0
59 | var minHeight: CGFloat = 48.0
60 |
61 | // Used to determine which components of the bounds we'll be modifying, based upon where the user's touch started.
62 | var anchorPoint = RKUserResizableViewAnchorPoint()
63 |
64 | weak var delegate: RKUserResizableViewDelegate?
65 |
66 | /* Let's inset everything that's drawn (the handles and the content view)
67 | so that users can trigger a resize from a few pixels outside of
68 | what they actually see as the bounding box. */
69 | let RKUserResizableViewGlobalInset:CGFloat = 5.0
70 | let RKUserResizableViewDefaultMinWidth:CGFloat = 48.0
71 | let RKUserResizableViewDefaultMinHeight:CGFloat = 48.0
72 | let RKUserResizableViewInteractiveBorderSize:CGFloat = 10.0
73 |
74 | // Defaults to YES. Disables the user from dragging the view outside the parent view's bounds.
75 |
76 | var isPreventsPositionOutsideSuperview: Bool = true
77 |
78 | private var RKUserResizableViewNoResizeAnchorPoint = RKUserResizableViewAnchorPoint(adjustsX: 0.0, adjustsY: 0.0, adjustsH: 0.0, adjustsW: 0.0)
79 | private var RKUserResizableViewUpperLeftAnchorPoint = RKUserResizableViewAnchorPoint(adjustsX: 1.0, adjustsY: 1.0, adjustsH: -1.0, adjustsW: 1.0)
80 | private var RKUserResizableViewMiddleLeftAnchorPoint = RKUserResizableViewAnchorPoint(adjustsX: 1.0, adjustsY: 0.0, adjustsH: 0.0, adjustsW: 1.0)
81 | private var RKUserResizableViewLowerLeftAnchorPoint = RKUserResizableViewAnchorPoint(adjustsX: 1.0, adjustsY: 0.0, adjustsH: 1.0, adjustsW: 1.0)
82 | private var RKUserResizableViewUpperMiddleAnchorPoint = RKUserResizableViewAnchorPoint(adjustsX: 0.0, adjustsY: 1.0, adjustsH: -1.0, adjustsW: 0.0)
83 | private var RKUserResizableViewUpperRightAnchorPoint = RKUserResizableViewAnchorPoint(adjustsX: 0.0, adjustsY: 1.0, adjustsH: -1.0, adjustsW: -1.0)
84 | private var RKUserResizableViewMiddleRightAnchorPoint = RKUserResizableViewAnchorPoint(adjustsX: 0.0, adjustsY: 0.0, adjustsH: 0.0, adjustsW: -1.0)
85 | private var RKUserResizableViewLowerRightAnchorPoint = RKUserResizableViewAnchorPoint(adjustsX: 0.0, adjustsY: 0.0, adjustsH: 1.0, adjustsW: -1.0)
86 | private var RKUserResizableViewLowerMiddleAnchorPoint = RKUserResizableViewAnchorPoint(adjustsX: 0.0, adjustsY: 0.0, adjustsH: 1.0, adjustsW: 0.0)
87 |
88 | func setupDefaultAttributes() {
89 | borderView = GripViewBorderView(frame: bounds.insetBy(dx: CGFloat(RKUserResizableViewGlobalInset), dy: CGFloat(RKUserResizableViewGlobalInset)))
90 | borderView?.isHidden = true
91 | self.addSubview(borderView!)
92 | minWidth = RKUserResizableViewDefaultMinWidth
93 | minHeight = RKUserResizableViewDefaultMinHeight
94 | isPreventsPositionOutsideSuperview = true
95 | }
96 |
97 | override init(frame: CGRect) {
98 | super.init(frame: frame)
99 | setupDefaultAttributes()
100 | }
101 |
102 | required init?(coder aDecoder: NSCoder) {
103 | super.init(coder: aDecoder)
104 | setupDefaultAttributes()
105 | }
106 |
107 | func setViewFrame(_ newFrame: CGRect) {
108 | self.frame = newFrame
109 | contentView?.frame = bounds.insetBy(dx: RKUserResizableViewGlobalInset + RKUserResizableViewInteractiveBorderSize / 2, dy: RKUserResizableViewGlobalInset + RKUserResizableViewInteractiveBorderSize / 2)
110 | borderView?.frame = bounds.insetBy(dx: RKUserResizableViewGlobalInset, dy: RKUserResizableViewGlobalInset)
111 | borderView?.setNeedsDisplay()
112 |
113 | }
114 |
115 | private func distanceBetweenTwoPoints(point1: CGPoint, point2: CGPoint) -> CGFloat {
116 | let dx: CGFloat = point2.x - point1.x
117 | let dy: CGFloat = point2.y - point1.y
118 | return sqrt(dx * dx + dy * dy)
119 | }
120 |
121 | func anchorPoint(forTouchLocation touchPoint: CGPoint) -> RKUserResizableViewAnchorPoint {
122 | // (1) Calculate the positions of each of the anchor points.
123 | let upperLeft = RKUserResizableViewAnchorPointPair(point: CGPoint(x: 0.0, y: 0.0), anchorPoint: RKUserResizableViewUpperLeftAnchorPoint)
124 |
125 | let upperMiddle = RKUserResizableViewAnchorPointPair(point: CGPoint(x: bounds.size.width / 2, y: 0.0), anchorPoint: RKUserResizableViewUpperMiddleAnchorPoint)
126 |
127 | let upperRight = RKUserResizableViewAnchorPointPair(point: CGPoint(x: bounds.size.width, y: 0.0), anchorPoint: RKUserResizableViewUpperRightAnchorPoint)
128 |
129 | let middleRight = RKUserResizableViewAnchorPointPair(point: CGPoint(x: bounds.size.width, y: bounds.size.height / 2), anchorPoint: RKUserResizableViewMiddleRightAnchorPoint)
130 |
131 | let lowerRight = RKUserResizableViewAnchorPointPair(point: CGPoint(x: bounds.size.width, y: bounds.size.height), anchorPoint: RKUserResizableViewLowerRightAnchorPoint)
132 |
133 | let lowerMiddle = RKUserResizableViewAnchorPointPair(point: CGPoint(x: bounds.size.width / 2, y: bounds.size.height), anchorPoint: RKUserResizableViewLowerMiddleAnchorPoint)
134 |
135 | let lowerLeft = RKUserResizableViewAnchorPointPair(point: CGPoint(x: 0, y: bounds.size.height), anchorPoint: RKUserResizableViewLowerLeftAnchorPoint)
136 |
137 | let middleLeft = RKUserResizableViewAnchorPointPair(point: CGPoint(x: 0, y: bounds.size.height / 2), anchorPoint: RKUserResizableViewMiddleLeftAnchorPoint)
138 |
139 | let centerPoint = RKUserResizableViewAnchorPointPair(point: CGPoint(x: bounds.size.width / 2, y: bounds.size.height / 2), anchorPoint: RKUserResizableViewNoResizeAnchorPoint)
140 |
141 | // (2) Iterate over each of the anchor points and find the one closest to the user's touch.
142 | let allPoints: [RKUserResizableViewAnchorPointPair] = [upperLeft, upperRight, lowerRight, lowerLeft, upperMiddle, lowerMiddle, middleLeft, middleRight, centerPoint]
143 | var smallestDistance: CGFloat = CGFloat(MAXFLOAT)
144 | var closestPoint: RKUserResizableViewAnchorPointPair = centerPoint
145 | for i in 0..<9 {
146 | let distance: CGFloat = distanceBetweenTwoPoints(point1: touchPoint, point2: allPoints[i].point)
147 | if distance < smallestDistance {
148 | closestPoint = allPoints[i]
149 | smallestDistance = distance
150 | }
151 | }
152 | return closestPoint.anchorPoint
153 | }
154 |
155 | func isResizing() -> Bool {
156 | return (anchorPoint.adjustsH != 0 || anchorPoint.adjustsW != 0 || anchorPoint.adjustsX != 0 || anchorPoint.adjustsY != 0)
157 | }
158 |
159 | override func touchesBegan(_ touches: Set, with event: UIEvent?) {
160 | super.touchesBegan(touches, with: event)
161 | // Notify the delegate we've begun our editing session.
162 | // if delegate?.responds(to: #selector(self.userResizableViewDidBeginEditing)) {
163 | delegate?.userResizableViewDidBeginEditing(self)
164 | // }
165 | borderView?.isHidden = false
166 |
167 | if let touch = touches.first {
168 | anchorPoint = anchorPoint(forTouchLocation: touch.location(in: self))
169 | print(anchorPoint)
170 | // When resizing, all calculations are done in the superview's coordinate space.
171 | touchStart = touch.location(in: superview)
172 | if !isResizing() {
173 | // When translating, all calculations are done in the view's coordinate space.
174 | touchStart = touch.location(in: self)
175 | }
176 | }
177 | }
178 |
179 | override func touchesEnded(_ touches: Set, with event: UIEvent?) {
180 | // Notify the delegate we've ended our editing session.
181 | delegate?.userResizableViewDidEndEditing(self)
182 | }
183 |
184 | override func touchesCancelled(_ touches: Set, with event: UIEvent?) {
185 | // Notify the delegate we've ended our editing session.
186 | delegate?.userResizableViewDidEndEditing(self)
187 | }
188 |
189 | func showEditingHandles() {
190 | borderView?.isHidden = false
191 | }
192 |
193 | func hideEditingHandles() {
194 | borderView?.isHidden = true
195 | }
196 |
197 | func resize(usingTouchLocation touchPoint: CGPoint) {
198 | // (1) Update the touch point if we're outside the superview.
199 | /* disabled by rajat jain on 2017-08-28
200 | if isPreventsPositionOutsideSuperview {
201 | let border: CGFloat = RKUserResizableViewGlobalInset + RKUserResizableViewInteractiveBorderSize / 2
202 | if touchPoint.x < border {
203 | touchPoint.x = border
204 | }
205 | if touchPoint.x > superview?.bounds.size.width - border {
206 | touchPoint.x = superview?.bounds.size.width - border
207 | }
208 | if touchPoint.y < border {
209 | touchPoint.y = border
210 | }
211 | if touchPoint.y > superview?.bounds.size.height - border {
212 | touchPoint.y = superview?.bounds.size.height - border
213 | }
214 | }
215 | */
216 | // (2) Calculate the deltas using the current anchor point.
217 | var deltaW: CGFloat = anchorPoint.adjustsW * (touchStart.x - touchPoint.x)
218 | let deltaX: CGFloat = anchorPoint.adjustsX * (-1.0 * deltaW)
219 | var deltaH: CGFloat = anchorPoint.adjustsH * (touchPoint.y - touchStart.y)
220 | let deltaY: CGFloat = anchorPoint.adjustsY * (-1.0 * deltaH)
221 | // (3) Calculate the new frame.
222 | var newX: CGFloat = frame.origin.x + deltaX
223 | var newY: CGFloat = frame.origin.y + deltaY
224 | var newWidth: CGFloat = frame.size.width + deltaW
225 | var newHeight: CGFloat = frame.size.height + deltaH
226 | // (4) If the new frame is too small, cancel the changes.
227 | if newWidth < minWidth {
228 | newWidth = frame.size.width
229 | newX = frame.origin.x
230 | }
231 | if newHeight < minHeight {
232 | newHeight = frame.size.height
233 | newY = frame.origin.y
234 | }
235 |
236 | // (5) Ensure the resize won't cause the view to move offscreen.
237 | if isPreventsPositionOutsideSuperview {
238 | if let superView = self.superview {
239 | if newX < superView.bounds.origin.x {
240 | // Calculate how much to grow the width by such that the new X coordintae will align with the superview.
241 | deltaW = self.frame.origin.x - superView.bounds.origin.x
242 | newWidth = self.frame.size.width + deltaW
243 | newX = superView.bounds.origin.x
244 | }
245 |
246 | if newX + newWidth > superView.bounds.origin.x + superView.bounds.size.width {
247 | newWidth = superView.bounds.size.width - newX
248 | }
249 |
250 | if newY < superView.bounds.origin.y {
251 | // Calculate how much to grow the height by such that the new Y coordintae will align with the superview.
252 | deltaH = self.frame.origin.y - superView.bounds.origin.y
253 | newHeight = self.frame.size.height + deltaH
254 | newY = superView.bounds.origin.y
255 | }
256 |
257 | if newY + newHeight > superView.bounds.origin.y + superView.bounds.size.height {
258 | newHeight = superView.bounds.size.height - newY
259 | }
260 |
261 | }
262 | }
263 |
264 | self.setViewFrame(CGRect(x: newX, y: newY, width: newWidth, height: newHeight))
265 |
266 | touchStart = touchPoint
267 | }
268 |
269 | func translate(usingTouchLocation touchPoint: CGPoint) {
270 | var newCenter = CGPoint(x: center.x + touchPoint.x - touchStart.x, y: center.y + touchPoint.y - touchStart.y)
271 | if isPreventsPositionOutsideSuperview {
272 | if let superView = self.superview {
273 |
274 | // Ensure the translation won't cause the view to move offscreen.
275 | let midPointX: CGFloat = bounds.midX
276 | if newCenter.x > superView.bounds.size.width - midPointX {
277 | newCenter.x = superView.bounds.size.width - midPointX
278 | }
279 | if newCenter.x < midPointX {
280 | newCenter.x = midPointX
281 | }
282 | let midPointY: CGFloat = bounds.midY
283 | if newCenter.y > superView.bounds.size.height - midPointY {
284 | newCenter.y = superView.bounds.size.height - midPointY
285 | }
286 | if newCenter.y < midPointY {
287 | newCenter.y = midPointY
288 | }
289 | }
290 | }
291 | center = newCenter
292 | }
293 |
294 | override func touchesMoved(_ touches: Set, with event: UIEvent?) {
295 | if isResizing() {
296 | if let superView = self.superview {
297 | resize(usingTouchLocation: (touches.first?.location(in: superView))!)
298 | }
299 | }
300 | else {
301 | translate(usingTouchLocation: (touches.first?.location(in: self))!)
302 | }
303 | }
304 |
305 | deinit {
306 | contentView?.removeFromSuperview()
307 | }
308 | }
309 |
310 |
311 | class GripViewBorderView: UIView {
312 |
313 | let RKUserResizableViewGlobalInset:CGFloat = 5.0
314 | let RKUserResizableViewDefaultMinWidth:CGFloat = 48.0
315 | let RKUserResizableViewDefaultMinHeight:CGFloat = 48.0
316 | let RKUserResizableViewInteractiveBorderSize:CGFloat = 10.0
317 |
318 | weak var delegate: RKUserResizableViewDelegate?
319 | // Will be retained as a subview.
320 | var contentView: UIView?
321 | // Default is 48.0 for each.
322 | var minWidth: CGFloat = 0.0
323 | var minHeight: CGFloat = 0.0
324 | // Defaults to YES. Disables the user from dragging the view outside the parent view's bounds.
325 | var isPreventsPositionOutsideSuperview: Bool = false
326 |
327 | var borderView: GripViewBorderView?
328 | var touchStart = CGPoint.zero
329 | // Used to determine which components of the bounds we'll be modifying, based upon where the user's touch started.
330 | var anchorPoint = RKUserResizableViewAnchorPoint()
331 |
332 | func hideEditingHandles() {
333 | }
334 |
335 | func showEditingHandles() {
336 | }
337 |
338 | override init(frame: CGRect) {
339 | // Clear background to ensure the content view shows through.
340 | super.init(frame: frame)
341 | self.backgroundColor = .clear
342 | }
343 |
344 | required init?(coder aDecoder: NSCoder) {
345 | fatalError("init(coder:) has not been implemented")
346 | }
347 |
348 | override func draw(_ rect: CGRect) {
349 | let context: CGContext? = UIGraphicsGetCurrentContext()
350 | context?.saveGState()
351 |
352 | // (1) Draw the bounding box.
353 | context?.setLineWidth(1.0)
354 | context?.setStrokeColor(UIColor.blue.cgColor)
355 | context?.addRect(bounds.insetBy(dx: RKUserResizableViewInteractiveBorderSize / 2, dy: RKUserResizableViewInteractiveBorderSize / 2))
356 | context?.strokePath()
357 |
358 | // (2) Calculate the bounding boxes for each of the anchor points.
359 | let upperLeft = CGRect(x: 0.0, y: 0.0, width: RKUserResizableViewInteractiveBorderSize, height: RKUserResizableViewInteractiveBorderSize)
360 | let upperRight = CGRect(x: bounds.size.width - RKUserResizableViewInteractiveBorderSize, y: 0.0, width: RKUserResizableViewInteractiveBorderSize, height: RKUserResizableViewInteractiveBorderSize)
361 | let lowerRight = CGRect(x: bounds.size.width - RKUserResizableViewInteractiveBorderSize, y: bounds.size.height - RKUserResizableViewInteractiveBorderSize, width: RKUserResizableViewInteractiveBorderSize, height: RKUserResizableViewInteractiveBorderSize)
362 | let lowerLeft = CGRect(x: 0.0, y: bounds.size.height - RKUserResizableViewInteractiveBorderSize, width: RKUserResizableViewInteractiveBorderSize, height: RKUserResizableViewInteractiveBorderSize)
363 | let upperMiddle = CGRect(x: (bounds.size.width - RKUserResizableViewInteractiveBorderSize) / 2, y: 0.0, width: RKUserResizableViewInteractiveBorderSize, height: RKUserResizableViewInteractiveBorderSize)
364 | let lowerMiddle = CGRect(x: (bounds.size.width - RKUserResizableViewInteractiveBorderSize) / 2, y: bounds.size.height - RKUserResizableViewInteractiveBorderSize, width: RKUserResizableViewInteractiveBorderSize, height: RKUserResizableViewInteractiveBorderSize)
365 | let middleLeft = CGRect(x: 0.0, y: (bounds.size.height - RKUserResizableViewInteractiveBorderSize) / 2, width: RKUserResizableViewInteractiveBorderSize, height: RKUserResizableViewInteractiveBorderSize)
366 | let middleRight = CGRect(x: bounds.size.width - RKUserResizableViewInteractiveBorderSize, y: (bounds.size.height - RKUserResizableViewInteractiveBorderSize) / 2, width: RKUserResizableViewInteractiveBorderSize, height: RKUserResizableViewInteractiveBorderSize)
367 |
368 | // (3) Create the gradient to paint the anchor points.
369 | let colors: [CGFloat] = [0.4, 0.8, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0]
370 | let baseSpace: CGColorSpace? = CGColorSpaceCreateDeviceRGB()
371 | let gradient: CGGradient = CGGradient(colorSpace: baseSpace!, colorComponents: colors, locations: nil, count: 2)!
372 |
373 | // (4) Set up the stroke for drawing the border of each of the anchor points.
374 | context?.setLineWidth(2)
375 | context!.setShadow(offset: CGSize(width: 0.5, height: 0.5), blur: 1)
376 | context?.setStrokeColor(UIColor.white.cgColor)
377 |
378 | // (5) Fill each anchor point using the gradient, then stroke the border.
379 | let allPoints: [CGRect] = [upperLeft, upperRight, lowerRight, lowerLeft, upperMiddle, lowerMiddle, middleLeft, middleRight]
380 | for i in 0..<8 {
381 | let currPoint: CGRect = allPoints[i]
382 | context?.saveGState()
383 | //context?.addEllipse(in: currPoint)
384 | let currentPoint = CGRect(x: currPoint.origin.x, y: currPoint.origin.y, width: 10, height: 10);
385 | context?.addEllipse(in: currentPoint)
386 | context?.clip()
387 | let startPoint = CGPoint(x: currentPoint.midX, y: currentPoint.minY)
388 | let endPoint = CGPoint(x: currentPoint.midX, y: currentPoint.maxY)
389 | context?.drawLinearGradient(gradient, start: startPoint, end: endPoint, options: [])
390 | context?.restoreGState()
391 | context?.strokeEllipse(in: currentPoint.insetBy(dx: 1, dy: 1))
392 | }
393 | context?.restoreGState()
394 | }
395 | }
396 |
397 |
--------------------------------------------------------------------------------