├── README.md ├── StickerView.swift └── StickerViewSample ├── Frameworks └── StickerView │ └── StickerView.swift ├── StickerViewSample.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── leeliangliang.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── leeliangliang.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist └── StickerViewSample ├── AppDelegate.swift ├── Assets.xcassets ├── AppIcon.appiconset │ └── Contents.json ├── Close.imageset │ ├── Contents.json │ └── close2@3x.png ├── Contents.json ├── Flip.imageset │ ├── Contents.json │ └── Flip.png ├── HotDog.imageset │ ├── Contents.json │ └── hot dog.png └── Rotate.imageset │ ├── Contents.json │ └── RoundRotate@3x.png ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard ├── Info.plist └── ViewController.swift /README.md: -------------------------------------------------------------------------------- 1 | # StickerView 2 | This is a sticker view with which you can zoom in/out, rotate, flip and move any kind of views by one touch handlers. (Swift 4, iOS 11.2 version) 3 | 4 | 5 | # What you can do with StickerView 6 | - add view which contains container view and handlers. 7 | - move 8 | - rotate / scale 9 | - flip 10 | - customize hanlders' image, size and visibility 11 | 12 | # Installation 13 | Import StickerView.swift to your Xcode project. That's all. 14 | 15 | # How to use 16 | You can have a look the sample project how to use StickerView, its easy to use and well commented. 17 | -------------------------------------------------------------------------------- /StickerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StickerView.swift 3 | // StickerView 4 | // 5 | // Created by Edgar Sia on 1/12/18. 6 | // Copyright © 2018 Edgar Sia. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | enum StickerViewHandler:Int { 12 | case close = 0 13 | case rotate 14 | case flip 15 | } 16 | 17 | enum StickerViewPosition:Int { 18 | case topLeft = 0 19 | case topRight 20 | case bottomLeft 21 | case bottomRight 22 | } 23 | 24 | @inline(__always) func CGRectGetCenter(_ rect:CGRect) -> CGPoint { 25 | return CGPoint(x: rect.midX, y: rect.midY) 26 | } 27 | 28 | @inline(__always) func CGRectScale(_ rect:CGRect, wScale:CGFloat, hScale:CGFloat) -> CGRect { 29 | return CGRect(x: rect.origin.x, y: rect.origin.y, width: rect.size.width * wScale, height: rect.size.height * hScale) 30 | } 31 | 32 | @inline(__always) func CGAffineTransformGetAngle(_ t:CGAffineTransform) -> CGFloat { 33 | return atan2(t.b, t.a) 34 | } 35 | 36 | @inline(__always) func CGPointGetDistance(point1:CGPoint, point2:CGPoint) -> CGFloat { 37 | let fx = point2.x - point1.x 38 | let fy = point2.y - point1.y 39 | return sqrt(fx * fx + fy * fy) 40 | } 41 | 42 | @objc protocol StickerViewDelegate { 43 | @objc func stickerViewDidBeginMoving(_ stickerView: StickerView) 44 | @objc func stickerViewDidChangeMoving(_ stickerView: StickerView) 45 | @objc func stickerViewDidEndMoving(_ stickerView: StickerView) 46 | @objc func stickerViewDidBeginRotating(_ stickerView: StickerView) 47 | @objc func stickerViewDidChangeRotating(_ stickerView: StickerView) 48 | @objc func stickerViewDidEndRotating(_ stickerView: StickerView) 49 | @objc func stickerViewDidClose(_ stickerView: StickerView) 50 | @objc func stickerViewDidTap(_ stickerView: StickerView) 51 | } 52 | 53 | class StickerView: UIView { 54 | var delegate: StickerViewDelegate! 55 | /// The contentView inside the sticker view. 56 | var contentView:UIView! 57 | /// Enable the close handler or not. Default value is YES. 58 | var enableClose:Bool = true { 59 | didSet { 60 | if self.showEditingHandlers { 61 | self.setEnableClose(self.enableClose) 62 | } 63 | } 64 | } 65 | /// Enable the rotate/resize handler or not. Default value is YES. 66 | var enableRotate:Bool = true{ 67 | didSet { 68 | if self.showEditingHandlers { 69 | self.setEnableRotate(self.enableRotate) 70 | } 71 | } 72 | } 73 | /// Enable the flip handler or not. Default value is YES. 74 | var enableFlip:Bool = true 75 | /// Show close and rotate/resize handlers or not. Default value is YES. 76 | var showEditingHandlers:Bool = true { 77 | didSet { 78 | if self.showEditingHandlers { 79 | self.setEnableClose(self.enableClose) 80 | self.setEnableRotate(self.enableRotate) 81 | self.setEnableFlip(self.enableFlip) 82 | self.contentView?.layer.borderWidth = 1 83 | } 84 | else { 85 | self.setEnableClose(false) 86 | self.setEnableRotate(false) 87 | self.setEnableFlip(false) 88 | self.contentView?.layer.borderWidth = 0 89 | } 90 | } 91 | } 92 | 93 | /// Minimum value for the shorter side while resizing. Default value will be used if not set. 94 | private var _minimumSize:NSInteger = 0 95 | var minimumSize:NSInteger { 96 | set { 97 | _minimumSize = max(newValue, self.defaultMinimumSize) 98 | } 99 | get { 100 | return _minimumSize 101 | } 102 | } 103 | /// Color of the outline border. Default: brown color. 104 | private var _outlineBorderColor:UIColor = .clear 105 | var outlineBorderColor:UIColor { 106 | set { 107 | _outlineBorderColor = newValue 108 | self.contentView?.layer.borderColor = _outlineBorderColor.cgColor 109 | } 110 | get { 111 | return _outlineBorderColor 112 | } 113 | } 114 | /// A convenient property for you to store extra information. 115 | var userInfo:Any? 116 | 117 | /** 118 | * Initialize a sticker view. This is the designated initializer. 119 | * 120 | * @param contentView The contentView inside the sticker view. 121 | * You can access it via the `contentView` property. 122 | * 123 | * @return The sticker view. 124 | */ 125 | init(contentView: UIView) { 126 | self.defaultInset = 11 127 | self.defaultMinimumSize = 4 * self.defaultInset 128 | 129 | var frame = contentView.frame 130 | frame = CGRect(x: 0, y: 0, width: frame.size.width + CGFloat(self.defaultInset) * 2, height: frame.size.height + CGFloat(self.defaultInset) * 2) 131 | super.init(frame: frame) 132 | self.backgroundColor = UIColor.clear 133 | self.addGestureRecognizer(self.moveGesture) 134 | self.addGestureRecognizer(self.tapGesture) 135 | 136 | // Setup content view 137 | self.contentView = contentView 138 | self.contentView.center = CGRectGetCenter(self.bounds) 139 | self.contentView.isUserInteractionEnabled = false 140 | self.contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 141 | self.contentView.layer.allowsEdgeAntialiasing = true 142 | self.addSubview(self.contentView) 143 | 144 | // Setup editing handlers 145 | self.setPosition(.topLeft, forHandler: .close) 146 | self.addSubview(self.closeImageView) 147 | self.setPosition(.bottomRight, forHandler: .rotate) 148 | self.addSubview(self.rotateImageView) 149 | self.setPosition(.bottomLeft, forHandler: .flip) 150 | self.addSubview(self.flipImageView) 151 | 152 | self.showEditingHandlers = true 153 | self.enableClose = true 154 | self.enableRotate = true 155 | self.enableFlip = true 156 | 157 | self.minimumSize = self.defaultMinimumSize 158 | self.outlineBorderColor = .brown 159 | } 160 | 161 | required init?(coder aDecoder: NSCoder) { 162 | fatalError("init(coder:) has not been implemented") 163 | } 164 | 165 | /** 166 | * Use image to customize each editing handler. 167 | * It is your responsibility to set image for every editing handler. 168 | * 169 | * @param image The image to be used. 170 | * @param handler The editing handler. 171 | */ 172 | func setImage(_ image:UIImage, forHandler handler:StickerViewHandler) { 173 | switch handler { 174 | case .close: 175 | self.closeImageView.image = image 176 | case .rotate: 177 | self.rotateImageView.image = image 178 | case .flip: 179 | self.flipImageView.image = image 180 | } 181 | } 182 | 183 | /** 184 | * Customize each editing handler's position. 185 | * If not set, default position will be used. 186 | * @note It is your responsibility not to set duplicated position. 187 | * 188 | * @param position The position for the handler. 189 | * @param handler The editing handler. 190 | */ 191 | func setPosition(_ position:StickerViewPosition, forHandler handler:StickerViewHandler) { 192 | let origin = self.contentView.frame.origin 193 | let size = self.contentView.frame.size 194 | 195 | var handlerView:UIImageView? 196 | switch handler { 197 | case .close: 198 | handlerView = self.closeImageView 199 | case .rotate: 200 | handlerView = self.rotateImageView 201 | case .flip: 202 | handlerView = self.flipImageView 203 | } 204 | 205 | switch position { 206 | case .topLeft: 207 | handlerView?.center = origin 208 | handlerView?.autoresizingMask = [.flexibleRightMargin, .flexibleBottomMargin] 209 | case .topRight: 210 | handlerView?.center = CGPoint(x: origin.x + size.width, y: origin.y) 211 | handlerView?.autoresizingMask = [.flexibleLeftMargin, .flexibleBottomMargin] 212 | case .bottomLeft: 213 | handlerView?.center = CGPoint(x: origin.x, y: origin.y + size.height) 214 | handlerView?.autoresizingMask = [.flexibleRightMargin, .flexibleTopMargin] 215 | case .bottomRight: 216 | handlerView?.center = CGPoint(x: origin.x + size.width, y: origin.y + size.height) 217 | handlerView?.autoresizingMask = [.flexibleLeftMargin, .flexibleTopMargin] 218 | } 219 | 220 | handlerView?.tag = position.rawValue 221 | } 222 | 223 | /** 224 | * Customize handler's size 225 | * 226 | * @param size Handler's size 227 | */ 228 | func setHandlerSize(_ size:Int) { 229 | if size <= 0 { 230 | return 231 | } 232 | 233 | self.defaultInset = NSInteger(round(Float(size) / 2)) 234 | self.defaultMinimumSize = 4 * self.defaultInset 235 | self.minimumSize = max(self.minimumSize, self.defaultMinimumSize) 236 | 237 | let originalCenter = self.center 238 | let originalTransform = self.transform 239 | var frame = self.contentView.frame 240 | frame = CGRect(x: 0, y: 0, width: frame.size.width + CGFloat(self.defaultInset) * 2, height: frame.size.height + CGFloat(self.defaultInset) * 2) 241 | 242 | self.contentView.removeFromSuperview() 243 | 244 | self.transform = CGAffineTransform.identity 245 | self.frame = frame 246 | 247 | self.contentView.center = CGRectGetCenter(self.bounds) 248 | self.addSubview(self.contentView) 249 | self.sendSubview(toBack: self.contentView) 250 | 251 | let handlerFrame = CGRect(x: 0, y: 0, width: self.defaultInset * 2, height: self.defaultInset * 2) 252 | self.closeImageView.frame = handlerFrame 253 | self.setPosition(StickerViewPosition(rawValue: self.closeImageView.tag)!, forHandler: .close) 254 | self.rotateImageView.frame = handlerFrame 255 | self.setPosition(StickerViewPosition(rawValue: self.rotateImageView.tag)!, forHandler: .rotate) 256 | self.flipImageView.frame = handlerFrame 257 | self.setPosition(StickerViewPosition(rawValue: self.flipImageView.tag)!, forHandler: .flip) 258 | 259 | self.center = originalCenter 260 | self.transform = originalTransform 261 | } 262 | 263 | /** 264 | * Default value 265 | */ 266 | private var defaultInset:NSInteger 267 | private var defaultMinimumSize:NSInteger 268 | 269 | /** 270 | * Variables for moving view 271 | */ 272 | private var beginningPoint = CGPoint.zero 273 | private var beginningCenter = CGPoint.zero 274 | 275 | /** 276 | * Variables for rotating and resizing view 277 | */ 278 | private var initialBounds = CGRect.zero 279 | private var initialDistance:CGFloat = 0 280 | private var deltaAngle:CGFloat = 0 281 | 282 | private lazy var moveGesture = { 283 | return UIPanGestureRecognizer(target: self, action: #selector(handleMoveGesture(_:))) 284 | }() 285 | private lazy var rotateImageView:UIImageView = { 286 | let rotateImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: self.defaultInset * 2, height: self.defaultInset * 2)) 287 | rotateImageView.contentMode = UIViewContentMode.scaleAspectFit 288 | rotateImageView.backgroundColor = UIColor.clear 289 | rotateImageView.isUserInteractionEnabled = true 290 | rotateImageView.addGestureRecognizer(self.rotateGesture) 291 | 292 | return rotateImageView 293 | }() 294 | private lazy var rotateGesture = { 295 | return UIPanGestureRecognizer(target: self, action: #selector(handleRotateGesture(_:))) 296 | }() 297 | private lazy var closeImageView:UIImageView = { 298 | let closeImageview = UIImageView(frame: CGRect(x: 0, y: 0, width: self.defaultInset * 2, height: self.defaultInset * 2)) 299 | closeImageview.contentMode = UIViewContentMode.scaleAspectFit 300 | closeImageview.backgroundColor = UIColor.clear 301 | closeImageview.isUserInteractionEnabled = true 302 | closeImageview.addGestureRecognizer(self.closeGesture) 303 | return closeImageview 304 | }() 305 | private lazy var closeGesture = { 306 | return UITapGestureRecognizer(target: self, action: #selector(handleCloseGesture(_:))) 307 | }() 308 | private lazy var flipImageView:UIImageView = { 309 | let flipImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: self.defaultInset * 2, height: self.defaultInset * 2)) 310 | flipImageView.contentMode = UIViewContentMode.scaleAspectFit 311 | flipImageView.backgroundColor = UIColor.clear 312 | flipImageView.isUserInteractionEnabled = true 313 | flipImageView.addGestureRecognizer(self.flipGesture) 314 | return flipImageView 315 | }() 316 | private lazy var flipGesture = { 317 | return UITapGestureRecognizer(target: self, action: #selector(handleFlipGesture(_:))) 318 | }() 319 | private lazy var tapGesture = { 320 | return UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(_:))) 321 | }() 322 | // MARK: - Gesture Handlers 323 | @objc 324 | func handleMoveGesture(_ recognizer: UIPanGestureRecognizer) { 325 | let touchLocation = recognizer.location(in: self.superview) 326 | switch recognizer.state { 327 | case .began: 328 | self.beginningPoint = touchLocation 329 | self.beginningCenter = self.center 330 | if let delegate = self.delegate { 331 | delegate.stickerViewDidBeginMoving(self) 332 | } 333 | case .changed: 334 | self.center = CGPoint(x: self.beginningCenter.x + (touchLocation.x - self.beginningPoint.x), y: self.beginningCenter.y + (touchLocation.y - self.beginningPoint.y)) 335 | if let delegate = self.delegate { 336 | delegate.stickerViewDidChangeMoving(self) 337 | } 338 | case .ended: 339 | self.center = CGPoint(x: self.beginningCenter.x + (touchLocation.x - self.beginningPoint.x), y: self.beginningCenter.y + (touchLocation.y - self.beginningPoint.y)) 340 | if let delegate = self.delegate { 341 | delegate.stickerViewDidEndMoving(self) 342 | } 343 | default: 344 | break 345 | } 346 | } 347 | 348 | @objc 349 | func handleRotateGesture(_ recognizer: UIPanGestureRecognizer) { 350 | let touchLocation = recognizer.location(in: self.superview) 351 | let center = self.center 352 | 353 | switch recognizer.state { 354 | case .began: 355 | self.deltaAngle = CGFloat(atan2f(Float(touchLocation.y - center.y), Float(touchLocation.x - center.x))) - CGAffineTransformGetAngle(self.transform) 356 | self.initialBounds = self.bounds 357 | self.initialDistance = CGPointGetDistance(point1: center, point2: touchLocation) 358 | if let delegate = self.delegate { 359 | delegate.stickerViewDidBeginRotating(self) 360 | } 361 | case .changed: 362 | let angle = atan2f(Float(touchLocation.y - center.y), Float(touchLocation.x - center.x)) 363 | let angleDiff = Float(self.deltaAngle) - angle 364 | self.transform = CGAffineTransform(rotationAngle: CGFloat(-angleDiff)) 365 | 366 | var scale = CGPointGetDistance(point1: center, point2: touchLocation) / self.initialDistance 367 | let minimumScale = CGFloat(self.minimumSize) / min(self.initialBounds.size.width, self.initialBounds.size.height) 368 | scale = max(scale, minimumScale) 369 | let scaledBounds = CGRectScale(self.initialBounds, wScale: scale, hScale: scale) 370 | self.bounds = scaledBounds 371 | self.setNeedsDisplay() 372 | 373 | if let delegate = self.delegate { 374 | delegate.stickerViewDidChangeRotating(self) 375 | } 376 | case .ended: 377 | if let delegate = self.delegate { 378 | delegate.stickerViewDidEndRotating(self) 379 | } 380 | default: 381 | break 382 | } 383 | } 384 | 385 | @objc 386 | func handleCloseGesture(_ recognizer: UITapGestureRecognizer) { 387 | if let delegate = self.delegate { 388 | delegate.stickerViewDidClose(self) 389 | } 390 | self.removeFromSuperview() 391 | } 392 | 393 | @objc 394 | func handleFlipGesture(_ recognizer: UITapGestureRecognizer) { 395 | UIView.animate(withDuration: 0.3) { 396 | self.contentView.transform = self.contentView.transform.scaledBy(x: -1, y: 1) 397 | } 398 | } 399 | 400 | @objc 401 | func handleTapGesture(_ recognizer: UITapGestureRecognizer) { 402 | if let delegate = self.delegate { 403 | delegate.stickerViewDidTap(self) 404 | } 405 | } 406 | 407 | // MARK: - Private Methods 408 | private func setEnableClose(_ enableClose:Bool) { 409 | self.closeImageView.isHidden = !enableClose 410 | self.closeImageView.isUserInteractionEnabled = enableClose 411 | } 412 | 413 | private func setEnableRotate(_ enableRotate:Bool) { 414 | self.rotateImageView.isHidden = !enableRotate 415 | self.rotateImageView.isUserInteractionEnabled = enableRotate 416 | } 417 | 418 | private func setEnableFlip(_ enableFlip:Bool) { 419 | self.flipImageView.isHidden = !enableFlip 420 | self.flipImageView.isUserInteractionEnabled = enableFlip 421 | } 422 | } 423 | 424 | extension StickerView: UIGestureRecognizerDelegate { 425 | func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool { 426 | /** 427 | * ref: http://stackoverflow.com/questions/19095165/should-superviews-gesture-cancel-subviews-gesture-in-ios-7/ 428 | * 429 | * The `gestureRecognizer` would be either closeGestureRecognizer or rotateGestureRecognizer, 430 | * `otherGestureRecognizer` should work only when `gestureRecognizer` is failed. 431 | * So, we always return YES here. 432 | */ 433 | return true 434 | } 435 | } 436 | -------------------------------------------------------------------------------- /StickerViewSample/Frameworks/StickerView/StickerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StickerView.swift 3 | // StickerView 4 | // 5 | // Created by Edgar Sia on 1/12/18. 6 | // Copyright © 2018 Edgar Sia. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | enum StickerViewHandler:Int { 12 | case close = 0 13 | case rotate 14 | case flip 15 | } 16 | 17 | enum StickerViewPosition:Int { 18 | case topLeft = 0 19 | case topRight 20 | case bottomLeft 21 | case bottomRight 22 | } 23 | 24 | @inline(__always) func CGRectGetCenter(_ rect:CGRect) -> CGPoint { 25 | return CGPoint(x: rect.midX, y: rect.midY) 26 | } 27 | 28 | @inline(__always) func CGRectScale(_ rect:CGRect, wScale:CGFloat, hScale:CGFloat) -> CGRect { 29 | return CGRect(x: rect.origin.x, y: rect.origin.y, width: rect.size.width * wScale, height: rect.size.height * hScale) 30 | } 31 | 32 | @inline(__always) func CGAffineTransformGetAngle(_ t:CGAffineTransform) -> CGFloat { 33 | return atan2(t.b, t.a) 34 | } 35 | 36 | @inline(__always) func CGPointGetDistance(point1:CGPoint, point2:CGPoint) -> CGFloat { 37 | let fx = point2.x - point1.x 38 | let fy = point2.y - point1.y 39 | return sqrt(fx * fx + fy * fy) 40 | } 41 | 42 | @objc protocol StickerViewDelegate { 43 | @objc func stickerViewDidBeginMoving(_ stickerView: StickerView) 44 | @objc func stickerViewDidChangeMoving(_ stickerView: StickerView) 45 | @objc func stickerViewDidEndMoving(_ stickerView: StickerView) 46 | @objc func stickerViewDidBeginRotating(_ stickerView: StickerView) 47 | @objc func stickerViewDidChangeRotating(_ stickerView: StickerView) 48 | @objc func stickerViewDidEndRotating(_ stickerView: StickerView) 49 | @objc func stickerViewDidClose(_ stickerView: StickerView) 50 | @objc func stickerViewDidTap(_ stickerView: StickerView) 51 | } 52 | 53 | class StickerView: UIView { 54 | var delegate: StickerViewDelegate! 55 | /// The contentView inside the sticker view. 56 | var contentView:UIView! 57 | /// Enable the close handler or not. Default value is YES. 58 | var enableClose:Bool = true { 59 | didSet { 60 | if self.showEditingHandlers { 61 | self.setEnableClose(self.enableClose) 62 | } 63 | } 64 | } 65 | /// Enable the rotate/resize handler or not. Default value is YES. 66 | var enableRotate:Bool = true{ 67 | didSet { 68 | if self.showEditingHandlers { 69 | self.setEnableRotate(self.enableRotate) 70 | } 71 | } 72 | } 73 | /// Enable the flip handler or not. Default value is YES. 74 | var enableFlip:Bool = true 75 | /// Show close and rotate/resize handlers or not. Default value is YES. 76 | var showEditingHandlers:Bool = true { 77 | didSet { 78 | if self.showEditingHandlers { 79 | self.setEnableClose(self.enableClose) 80 | self.setEnableRotate(self.enableRotate) 81 | self.setEnableFlip(self.enableFlip) 82 | self.contentView?.layer.borderWidth = 1 83 | } 84 | else { 85 | self.setEnableClose(false) 86 | self.setEnableRotate(false) 87 | self.setEnableFlip(false) 88 | self.contentView?.layer.borderWidth = 0 89 | } 90 | } 91 | } 92 | 93 | /// Minimum value for the shorter side while resizing. Default value will be used if not set. 94 | private var _minimumSize:NSInteger = 0 95 | var minimumSize:NSInteger { 96 | set { 97 | _minimumSize = max(newValue, self.defaultMinimumSize) 98 | } 99 | get { 100 | return _minimumSize 101 | } 102 | } 103 | /// Color of the outline border. Default: brown color. 104 | private var _outlineBorderColor:UIColor = .clear 105 | var outlineBorderColor:UIColor { 106 | set { 107 | _outlineBorderColor = newValue 108 | self.contentView?.layer.borderColor = _outlineBorderColor.cgColor 109 | } 110 | get { 111 | return _outlineBorderColor 112 | } 113 | } 114 | /// A convenient property for you to store extra information. 115 | var userInfo:Any? 116 | 117 | /** 118 | * Initialize a sticker view. This is the designated initializer. 119 | * 120 | * @param contentView The contentView inside the sticker view. 121 | * You can access it via the `contentView` property. 122 | * 123 | * @return The sticker view. 124 | */ 125 | init(contentView: UIView) { 126 | self.defaultInset = 11 127 | self.defaultMinimumSize = 4 * self.defaultInset 128 | 129 | var frame = contentView.frame 130 | frame = CGRect(x: 0, y: 0, width: frame.size.width + CGFloat(self.defaultInset) * 2, height: frame.size.height + CGFloat(self.defaultInset) * 2) 131 | super.init(frame: frame) 132 | self.backgroundColor = UIColor.clear 133 | self.addGestureRecognizer(self.moveGesture) 134 | self.addGestureRecognizer(self.tapGesture) 135 | 136 | // Setup content view 137 | self.contentView = contentView 138 | self.contentView.center = CGRectGetCenter(self.bounds) 139 | self.contentView.isUserInteractionEnabled = false 140 | self.contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 141 | self.contentView.layer.allowsEdgeAntialiasing = true 142 | self.addSubview(self.contentView) 143 | 144 | // Setup editing handlers 145 | self.setPosition(.topLeft, forHandler: .close) 146 | self.addSubview(self.closeImageView) 147 | self.setPosition(.bottomRight, forHandler: .rotate) 148 | self.addSubview(self.rotateImageView) 149 | self.setPosition(.bottomLeft, forHandler: .flip) 150 | self.addSubview(self.flipImageView) 151 | 152 | self.showEditingHandlers = true 153 | self.enableClose = true 154 | self.enableRotate = true 155 | self.enableFlip = true 156 | 157 | self.minimumSize = self.defaultMinimumSize 158 | self.outlineBorderColor = .brown 159 | } 160 | 161 | required init?(coder aDecoder: NSCoder) { 162 | fatalError("init(coder:) has not been implemented") 163 | } 164 | 165 | /** 166 | * Use image to customize each editing handler. 167 | * It is your responsibility to set image for every editing handler. 168 | * 169 | * @param image The image to be used. 170 | * @param handler The editing handler. 171 | */ 172 | func setImage(_ image:UIImage, forHandler handler:StickerViewHandler) { 173 | switch handler { 174 | case .close: 175 | self.closeImageView.image = image 176 | case .rotate: 177 | self.rotateImageView.image = image 178 | case .flip: 179 | self.flipImageView.image = image 180 | } 181 | } 182 | 183 | /** 184 | * Customize each editing handler's position. 185 | * If not set, default position will be used. 186 | * @note It is your responsibility not to set duplicated position. 187 | * 188 | * @param position The position for the handler. 189 | * @param handler The editing handler. 190 | */ 191 | func setPosition(_ position:StickerViewPosition, forHandler handler:StickerViewHandler) { 192 | let origin = self.contentView.frame.origin 193 | let size = self.contentView.frame.size 194 | 195 | var handlerView:UIImageView? 196 | switch handler { 197 | case .close: 198 | handlerView = self.closeImageView 199 | case .rotate: 200 | handlerView = self.rotateImageView 201 | case .flip: 202 | handlerView = self.flipImageView 203 | } 204 | 205 | switch position { 206 | case .topLeft: 207 | handlerView?.center = origin 208 | handlerView?.autoresizingMask = [.flexibleRightMargin, .flexibleBottomMargin] 209 | case .topRight: 210 | handlerView?.center = CGPoint(x: origin.x + size.width, y: origin.y) 211 | handlerView?.autoresizingMask = [.flexibleLeftMargin, .flexibleBottomMargin] 212 | case .bottomLeft: 213 | handlerView?.center = CGPoint(x: origin.x, y: origin.y + size.height) 214 | handlerView?.autoresizingMask = [.flexibleRightMargin, .flexibleTopMargin] 215 | case .bottomRight: 216 | handlerView?.center = CGPoint(x: origin.x + size.width, y: origin.y + size.height) 217 | handlerView?.autoresizingMask = [.flexibleLeftMargin, .flexibleTopMargin] 218 | } 219 | 220 | handlerView?.tag = position.rawValue 221 | } 222 | 223 | /** 224 | * Customize handler's size 225 | * 226 | * @param size Handler's size 227 | */ 228 | func setHandlerSize(_ size:Int) { 229 | if size <= 0 { 230 | return 231 | } 232 | 233 | self.defaultInset = NSInteger(round(Float(size) / 2)) 234 | self.defaultMinimumSize = 4 * self.defaultInset 235 | self.minimumSize = max(self.minimumSize, self.defaultMinimumSize) 236 | 237 | let originalCenter = self.center 238 | let originalTransform = self.transform 239 | var frame = self.contentView.frame 240 | frame = CGRect(x: 0, y: 0, width: frame.size.width + CGFloat(self.defaultInset) * 2, height: frame.size.height + CGFloat(self.defaultInset) * 2) 241 | 242 | self.contentView.removeFromSuperview() 243 | 244 | self.transform = CGAffineTransform.identity 245 | self.frame = frame 246 | 247 | self.contentView.center = CGRectGetCenter(self.bounds) 248 | self.addSubview(self.contentView) 249 | self.sendSubview(toBack: self.contentView) 250 | 251 | let handlerFrame = CGRect(x: 0, y: 0, width: self.defaultInset * 2, height: self.defaultInset * 2) 252 | self.closeImageView.frame = handlerFrame 253 | self.setPosition(StickerViewPosition(rawValue: self.closeImageView.tag)!, forHandler: .close) 254 | self.rotateImageView.frame = handlerFrame 255 | self.setPosition(StickerViewPosition(rawValue: self.rotateImageView.tag)!, forHandler: .rotate) 256 | self.flipImageView.frame = handlerFrame 257 | self.setPosition(StickerViewPosition(rawValue: self.flipImageView.tag)!, forHandler: .flip) 258 | 259 | self.center = originalCenter 260 | self.transform = originalTransform 261 | } 262 | 263 | /** 264 | * Default value 265 | */ 266 | private var defaultInset:NSInteger 267 | private var defaultMinimumSize:NSInteger 268 | 269 | /** 270 | * Variables for moving view 271 | */ 272 | private var beginningPoint = CGPoint.zero 273 | private var beginningCenter = CGPoint.zero 274 | 275 | /** 276 | * Variables for rotating and resizing view 277 | */ 278 | private var initialBounds = CGRect.zero 279 | private var initialDistance:CGFloat = 0 280 | private var deltaAngle:CGFloat = 0 281 | 282 | private lazy var moveGesture = { 283 | return UIPanGestureRecognizer(target: self, action: #selector(handleMoveGesture(_:))) 284 | }() 285 | private lazy var rotateImageView:UIImageView = { 286 | let rotateImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: self.defaultInset * 2, height: self.defaultInset * 2)) 287 | rotateImageView.contentMode = UIViewContentMode.scaleAspectFit 288 | rotateImageView.backgroundColor = UIColor.clear 289 | rotateImageView.isUserInteractionEnabled = true 290 | rotateImageView.addGestureRecognizer(self.rotateGesture) 291 | 292 | return rotateImageView 293 | }() 294 | private lazy var rotateGesture = { 295 | return UIPanGestureRecognizer(target: self, action: #selector(handleRotateGesture(_:))) 296 | }() 297 | private lazy var closeImageView:UIImageView = { 298 | let closeImageview = UIImageView(frame: CGRect(x: 0, y: 0, width: self.defaultInset * 2, height: self.defaultInset * 2)) 299 | closeImageview.contentMode = UIViewContentMode.scaleAspectFit 300 | closeImageview.backgroundColor = UIColor.clear 301 | closeImageview.isUserInteractionEnabled = true 302 | closeImageview.addGestureRecognizer(self.closeGesture) 303 | return closeImageview 304 | }() 305 | private lazy var closeGesture = { 306 | return UITapGestureRecognizer(target: self, action: #selector(handleCloseGesture(_:))) 307 | }() 308 | private lazy var flipImageView:UIImageView = { 309 | let flipImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: self.defaultInset * 2, height: self.defaultInset * 2)) 310 | flipImageView.contentMode = UIViewContentMode.scaleAspectFit 311 | flipImageView.backgroundColor = UIColor.clear 312 | flipImageView.isUserInteractionEnabled = true 313 | flipImageView.addGestureRecognizer(self.flipGesture) 314 | return flipImageView 315 | }() 316 | private lazy var flipGesture = { 317 | return UITapGestureRecognizer(target: self, action: #selector(handleFlipGesture(_:))) 318 | }() 319 | private lazy var tapGesture = { 320 | return UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(_:))) 321 | }() 322 | // MARK: - Gesture Handlers 323 | @objc 324 | func handleMoveGesture(_ recognizer: UIPanGestureRecognizer) { 325 | let touchLocation = recognizer.location(in: self.superview) 326 | switch recognizer.state { 327 | case .began: 328 | self.beginningPoint = touchLocation 329 | self.beginningCenter = self.center 330 | if let delegate = self.delegate { 331 | delegate.stickerViewDidBeginMoving(self) 332 | } 333 | case .changed: 334 | self.center = CGPoint(x: self.beginningCenter.x + (touchLocation.x - self.beginningPoint.x), y: self.beginningCenter.y + (touchLocation.y - self.beginningPoint.y)) 335 | if let delegate = self.delegate { 336 | delegate.stickerViewDidChangeMoving(self) 337 | } 338 | case .ended: 339 | self.center = CGPoint(x: self.beginningCenter.x + (touchLocation.x - self.beginningPoint.x), y: self.beginningCenter.y + (touchLocation.y - self.beginningPoint.y)) 340 | if let delegate = self.delegate { 341 | delegate.stickerViewDidEndMoving(self) 342 | } 343 | default: 344 | break 345 | } 346 | } 347 | 348 | @objc 349 | func handleRotateGesture(_ recognizer: UIPanGestureRecognizer) { 350 | let touchLocation = recognizer.location(in: self.superview) 351 | let center = self.center 352 | 353 | switch recognizer.state { 354 | case .began: 355 | self.deltaAngle = CGFloat(atan2f(Float(touchLocation.y - center.y), Float(touchLocation.x - center.x))) - CGAffineTransformGetAngle(self.transform) 356 | self.initialBounds = self.bounds 357 | self.initialDistance = CGPointGetDistance(point1: center, point2: touchLocation) 358 | if let delegate = self.delegate { 359 | delegate.stickerViewDidBeginRotating(self) 360 | } 361 | case .changed: 362 | let angle = atan2f(Float(touchLocation.y - center.y), Float(touchLocation.x - center.x)) 363 | let angleDiff = Float(self.deltaAngle) - angle 364 | self.transform = CGAffineTransform(rotationAngle: CGFloat(-angleDiff)) 365 | 366 | var scale = CGPointGetDistance(point1: center, point2: touchLocation) / self.initialDistance 367 | let minimumScale = CGFloat(self.minimumSize) / min(self.initialBounds.size.width, self.initialBounds.size.height) 368 | scale = max(scale, minimumScale) 369 | let scaledBounds = CGRectScale(self.initialBounds, wScale: scale, hScale: scale) 370 | self.bounds = scaledBounds 371 | self.setNeedsDisplay() 372 | 373 | if let delegate = self.delegate { 374 | delegate.stickerViewDidChangeRotating(self) 375 | } 376 | case .ended: 377 | if let delegate = self.delegate { 378 | delegate.stickerViewDidEndRotating(self) 379 | } 380 | default: 381 | break 382 | } 383 | } 384 | 385 | @objc 386 | func handleCloseGesture(_ recognizer: UITapGestureRecognizer) { 387 | if let delegate = self.delegate { 388 | delegate.stickerViewDidClose(self) 389 | } 390 | self.removeFromSuperview() 391 | } 392 | 393 | @objc 394 | func handleFlipGesture(_ recognizer: UITapGestureRecognizer) { 395 | UIView.animate(withDuration: 0.3) { 396 | self.contentView.transform = self.contentView.transform.scaledBy(x: -1, y: 1) 397 | } 398 | } 399 | 400 | @objc 401 | func handleTapGesture(_ recognizer: UITapGestureRecognizer) { 402 | if let delegate = self.delegate { 403 | delegate.stickerViewDidTap(self) 404 | } 405 | } 406 | 407 | // MARK: - Private Methods 408 | private func setEnableClose(_ enableClose:Bool) { 409 | self.closeImageView.isHidden = !enableClose 410 | self.closeImageView.isUserInteractionEnabled = enableClose 411 | } 412 | 413 | private func setEnableRotate(_ enableRotate:Bool) { 414 | self.rotateImageView.isHidden = !enableRotate 415 | self.rotateImageView.isUserInteractionEnabled = enableRotate 416 | } 417 | 418 | private func setEnableFlip(_ enableFlip:Bool) { 419 | self.flipImageView.isHidden = !enableFlip 420 | self.flipImageView.isUserInteractionEnabled = enableFlip 421 | } 422 | } 423 | 424 | extension StickerView: UIGestureRecognizerDelegate { 425 | func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool { 426 | /** 427 | * ref: http://stackoverflow.com/questions/19095165/should-superviews-gesture-cancel-subviews-gesture-in-ios-7/ 428 | * 429 | * The `gestureRecognizer` would be either closeGestureRecognizer or rotateGestureRecognizer, 430 | * `otherGestureRecognizer` should work only when `gestureRecognizer` is failed. 431 | * So, we always return YES here. 432 | */ 433 | return true 434 | } 435 | } 436 | -------------------------------------------------------------------------------- /StickerViewSample/StickerViewSample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 01AFFC0E202B106E00C3FC5B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01AFFC0D202B106E00C3FC5B /* AppDelegate.swift */; }; 11 | 01AFFC10202B106E00C3FC5B /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01AFFC0F202B106E00C3FC5B /* ViewController.swift */; }; 12 | 01AFFC13202B106E00C3FC5B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 01AFFC11202B106E00C3FC5B /* Main.storyboard */; }; 13 | 01AFFC15202B106E00C3FC5B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 01AFFC14202B106E00C3FC5B /* Assets.xcassets */; }; 14 | 01AFFC18202B106E00C3FC5B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 01AFFC16202B106E00C3FC5B /* LaunchScreen.storyboard */; }; 15 | 01AFFC22202B10A500C3FC5B /* StickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01AFFC21202B10A500C3FC5B /* StickerView.swift */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXFileReference section */ 19 | 01AFFC0A202B106E00C3FC5B /* StickerViewSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StickerViewSample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 20 | 01AFFC0D202B106E00C3FC5B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 21 | 01AFFC0F202B106E00C3FC5B /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 22 | 01AFFC12202B106E00C3FC5B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 23 | 01AFFC14202B106E00C3FC5B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 24 | 01AFFC17202B106E00C3FC5B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 25 | 01AFFC19202B106E00C3FC5B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 26 | 01AFFC21202B10A500C3FC5B /* StickerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerView.swift; sourceTree = ""; }; 27 | /* End PBXFileReference section */ 28 | 29 | /* Begin PBXFrameworksBuildPhase section */ 30 | 01AFFC07202B106E00C3FC5B /* Frameworks */ = { 31 | isa = PBXFrameworksBuildPhase; 32 | buildActionMask = 2147483647; 33 | files = ( 34 | ); 35 | runOnlyForDeploymentPostprocessing = 0; 36 | }; 37 | /* End PBXFrameworksBuildPhase section */ 38 | 39 | /* Begin PBXGroup section */ 40 | 01AFFC01202B106E00C3FC5B = { 41 | isa = PBXGroup; 42 | children = ( 43 | 01AFFC1F202B10A500C3FC5B /* Frameworks */, 44 | 01AFFC0C202B106E00C3FC5B /* StickerViewSample */, 45 | 01AFFC0B202B106E00C3FC5B /* Products */, 46 | ); 47 | sourceTree = ""; 48 | }; 49 | 01AFFC0B202B106E00C3FC5B /* Products */ = { 50 | isa = PBXGroup; 51 | children = ( 52 | 01AFFC0A202B106E00C3FC5B /* StickerViewSample.app */, 53 | ); 54 | name = Products; 55 | sourceTree = ""; 56 | }; 57 | 01AFFC0C202B106E00C3FC5B /* StickerViewSample */ = { 58 | isa = PBXGroup; 59 | children = ( 60 | 01AFFC0D202B106E00C3FC5B /* AppDelegate.swift */, 61 | 01AFFC0F202B106E00C3FC5B /* ViewController.swift */, 62 | 01AFFC11202B106E00C3FC5B /* Main.storyboard */, 63 | 01AFFC14202B106E00C3FC5B /* Assets.xcassets */, 64 | 01AFFC16202B106E00C3FC5B /* LaunchScreen.storyboard */, 65 | 01AFFC19202B106E00C3FC5B /* Info.plist */, 66 | ); 67 | path = StickerViewSample; 68 | sourceTree = ""; 69 | }; 70 | 01AFFC1F202B10A500C3FC5B /* Frameworks */ = { 71 | isa = PBXGroup; 72 | children = ( 73 | 01AFFC20202B10A500C3FC5B /* StickerView */, 74 | ); 75 | path = Frameworks; 76 | sourceTree = ""; 77 | }; 78 | 01AFFC20202B10A500C3FC5B /* StickerView */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 01AFFC21202B10A500C3FC5B /* StickerView.swift */, 82 | ); 83 | path = StickerView; 84 | sourceTree = ""; 85 | }; 86 | /* End PBXGroup section */ 87 | 88 | /* Begin PBXNativeTarget section */ 89 | 01AFFC09202B106E00C3FC5B /* StickerViewSample */ = { 90 | isa = PBXNativeTarget; 91 | buildConfigurationList = 01AFFC1C202B106E00C3FC5B /* Build configuration list for PBXNativeTarget "StickerViewSample" */; 92 | buildPhases = ( 93 | 01AFFC06202B106E00C3FC5B /* Sources */, 94 | 01AFFC07202B106E00C3FC5B /* Frameworks */, 95 | 01AFFC08202B106E00C3FC5B /* Resources */, 96 | ); 97 | buildRules = ( 98 | ); 99 | dependencies = ( 100 | ); 101 | name = StickerViewSample; 102 | productName = StickerViewSample; 103 | productReference = 01AFFC0A202B106E00C3FC5B /* StickerViewSample.app */; 104 | productType = "com.apple.product-type.application"; 105 | }; 106 | /* End PBXNativeTarget section */ 107 | 108 | /* Begin PBXProject section */ 109 | 01AFFC02202B106E00C3FC5B /* Project object */ = { 110 | isa = PBXProject; 111 | attributes = { 112 | LastSwiftUpdateCheck = 0920; 113 | LastUpgradeCheck = 0920; 114 | ORGANIZATIONNAME = "Edgar Sia"; 115 | TargetAttributes = { 116 | 01AFFC09202B106E00C3FC5B = { 117 | CreatedOnToolsVersion = 9.2; 118 | ProvisioningStyle = Automatic; 119 | }; 120 | }; 121 | }; 122 | buildConfigurationList = 01AFFC05202B106E00C3FC5B /* Build configuration list for PBXProject "StickerViewSample" */; 123 | compatibilityVersion = "Xcode 8.0"; 124 | developmentRegion = en; 125 | hasScannedForEncodings = 0; 126 | knownRegions = ( 127 | en, 128 | Base, 129 | ); 130 | mainGroup = 01AFFC01202B106E00C3FC5B; 131 | productRefGroup = 01AFFC0B202B106E00C3FC5B /* Products */; 132 | projectDirPath = ""; 133 | projectRoot = ""; 134 | targets = ( 135 | 01AFFC09202B106E00C3FC5B /* StickerViewSample */, 136 | ); 137 | }; 138 | /* End PBXProject section */ 139 | 140 | /* Begin PBXResourcesBuildPhase section */ 141 | 01AFFC08202B106E00C3FC5B /* Resources */ = { 142 | isa = PBXResourcesBuildPhase; 143 | buildActionMask = 2147483647; 144 | files = ( 145 | 01AFFC18202B106E00C3FC5B /* LaunchScreen.storyboard in Resources */, 146 | 01AFFC15202B106E00C3FC5B /* Assets.xcassets in Resources */, 147 | 01AFFC13202B106E00C3FC5B /* Main.storyboard in Resources */, 148 | ); 149 | runOnlyForDeploymentPostprocessing = 0; 150 | }; 151 | /* End PBXResourcesBuildPhase section */ 152 | 153 | /* Begin PBXSourcesBuildPhase section */ 154 | 01AFFC06202B106E00C3FC5B /* Sources */ = { 155 | isa = PBXSourcesBuildPhase; 156 | buildActionMask = 2147483647; 157 | files = ( 158 | 01AFFC10202B106E00C3FC5B /* ViewController.swift in Sources */, 159 | 01AFFC0E202B106E00C3FC5B /* AppDelegate.swift in Sources */, 160 | 01AFFC22202B10A500C3FC5B /* StickerView.swift in Sources */, 161 | ); 162 | runOnlyForDeploymentPostprocessing = 0; 163 | }; 164 | /* End PBXSourcesBuildPhase section */ 165 | 166 | /* Begin PBXVariantGroup section */ 167 | 01AFFC11202B106E00C3FC5B /* Main.storyboard */ = { 168 | isa = PBXVariantGroup; 169 | children = ( 170 | 01AFFC12202B106E00C3FC5B /* Base */, 171 | ); 172 | name = Main.storyboard; 173 | sourceTree = ""; 174 | }; 175 | 01AFFC16202B106E00C3FC5B /* LaunchScreen.storyboard */ = { 176 | isa = PBXVariantGroup; 177 | children = ( 178 | 01AFFC17202B106E00C3FC5B /* Base */, 179 | ); 180 | name = LaunchScreen.storyboard; 181 | sourceTree = ""; 182 | }; 183 | /* End PBXVariantGroup section */ 184 | 185 | /* Begin XCBuildConfiguration section */ 186 | 01AFFC1A202B106E00C3FC5B /* Debug */ = { 187 | isa = XCBuildConfiguration; 188 | buildSettings = { 189 | ALWAYS_SEARCH_USER_PATHS = NO; 190 | CLANG_ANALYZER_NONNULL = YES; 191 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 192 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 193 | CLANG_CXX_LIBRARY = "libc++"; 194 | CLANG_ENABLE_MODULES = YES; 195 | CLANG_ENABLE_OBJC_ARC = YES; 196 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 197 | CLANG_WARN_BOOL_CONVERSION = YES; 198 | CLANG_WARN_COMMA = YES; 199 | CLANG_WARN_CONSTANT_CONVERSION = YES; 200 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 201 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 202 | CLANG_WARN_EMPTY_BODY = YES; 203 | CLANG_WARN_ENUM_CONVERSION = YES; 204 | CLANG_WARN_INFINITE_RECURSION = YES; 205 | CLANG_WARN_INT_CONVERSION = YES; 206 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 207 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 208 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 209 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 210 | CLANG_WARN_STRICT_PROTOTYPES = YES; 211 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 212 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 213 | CLANG_WARN_UNREACHABLE_CODE = YES; 214 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 215 | CODE_SIGN_IDENTITY = "iPhone Developer"; 216 | COPY_PHASE_STRIP = NO; 217 | DEBUG_INFORMATION_FORMAT = dwarf; 218 | ENABLE_STRICT_OBJC_MSGSEND = YES; 219 | ENABLE_TESTABILITY = YES; 220 | GCC_C_LANGUAGE_STANDARD = gnu11; 221 | GCC_DYNAMIC_NO_PIC = NO; 222 | GCC_NO_COMMON_BLOCKS = YES; 223 | GCC_OPTIMIZATION_LEVEL = 0; 224 | GCC_PREPROCESSOR_DEFINITIONS = ( 225 | "DEBUG=1", 226 | "$(inherited)", 227 | ); 228 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 229 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 230 | GCC_WARN_UNDECLARED_SELECTOR = YES; 231 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 232 | GCC_WARN_UNUSED_FUNCTION = YES; 233 | GCC_WARN_UNUSED_VARIABLE = YES; 234 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 235 | MTL_ENABLE_DEBUG_INFO = YES; 236 | ONLY_ACTIVE_ARCH = YES; 237 | SDKROOT = iphoneos; 238 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 239 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 240 | }; 241 | name = Debug; 242 | }; 243 | 01AFFC1B202B106E00C3FC5B /* Release */ = { 244 | isa = XCBuildConfiguration; 245 | buildSettings = { 246 | ALWAYS_SEARCH_USER_PATHS = NO; 247 | CLANG_ANALYZER_NONNULL = YES; 248 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 249 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 250 | CLANG_CXX_LIBRARY = "libc++"; 251 | CLANG_ENABLE_MODULES = YES; 252 | CLANG_ENABLE_OBJC_ARC = YES; 253 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 254 | CLANG_WARN_BOOL_CONVERSION = YES; 255 | CLANG_WARN_COMMA = YES; 256 | CLANG_WARN_CONSTANT_CONVERSION = YES; 257 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 258 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 259 | CLANG_WARN_EMPTY_BODY = YES; 260 | CLANG_WARN_ENUM_CONVERSION = YES; 261 | CLANG_WARN_INFINITE_RECURSION = YES; 262 | CLANG_WARN_INT_CONVERSION = YES; 263 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 264 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 265 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 266 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 267 | CLANG_WARN_STRICT_PROTOTYPES = YES; 268 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 269 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 270 | CLANG_WARN_UNREACHABLE_CODE = YES; 271 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 272 | CODE_SIGN_IDENTITY = "iPhone Developer"; 273 | COPY_PHASE_STRIP = NO; 274 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 275 | ENABLE_NS_ASSERTIONS = NO; 276 | ENABLE_STRICT_OBJC_MSGSEND = YES; 277 | GCC_C_LANGUAGE_STANDARD = gnu11; 278 | GCC_NO_COMMON_BLOCKS = YES; 279 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 280 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 281 | GCC_WARN_UNDECLARED_SELECTOR = YES; 282 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 283 | GCC_WARN_UNUSED_FUNCTION = YES; 284 | GCC_WARN_UNUSED_VARIABLE = YES; 285 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 286 | MTL_ENABLE_DEBUG_INFO = NO; 287 | SDKROOT = iphoneos; 288 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 289 | VALIDATE_PRODUCT = YES; 290 | }; 291 | name = Release; 292 | }; 293 | 01AFFC1D202B106E00C3FC5B /* Debug */ = { 294 | isa = XCBuildConfiguration; 295 | buildSettings = { 296 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 297 | CODE_SIGN_STYLE = Automatic; 298 | DEVELOPMENT_TEAM = 57997T9892; 299 | INFOPLIST_FILE = StickerViewSample/Info.plist; 300 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 301 | PRODUCT_BUNDLE_IDENTIFIER = injap.sia.StickerViewSample; 302 | PRODUCT_NAME = "$(TARGET_NAME)"; 303 | SWIFT_VERSION = 4.0; 304 | TARGETED_DEVICE_FAMILY = "1,2"; 305 | }; 306 | name = Debug; 307 | }; 308 | 01AFFC1E202B106E00C3FC5B /* Release */ = { 309 | isa = XCBuildConfiguration; 310 | buildSettings = { 311 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 312 | CODE_SIGN_STYLE = Automatic; 313 | DEVELOPMENT_TEAM = 57997T9892; 314 | INFOPLIST_FILE = StickerViewSample/Info.plist; 315 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 316 | PRODUCT_BUNDLE_IDENTIFIER = injap.sia.StickerViewSample; 317 | PRODUCT_NAME = "$(TARGET_NAME)"; 318 | SWIFT_VERSION = 4.0; 319 | TARGETED_DEVICE_FAMILY = "1,2"; 320 | }; 321 | name = Release; 322 | }; 323 | /* End XCBuildConfiguration section */ 324 | 325 | /* Begin XCConfigurationList section */ 326 | 01AFFC05202B106E00C3FC5B /* Build configuration list for PBXProject "StickerViewSample" */ = { 327 | isa = XCConfigurationList; 328 | buildConfigurations = ( 329 | 01AFFC1A202B106E00C3FC5B /* Debug */, 330 | 01AFFC1B202B106E00C3FC5B /* Release */, 331 | ); 332 | defaultConfigurationIsVisible = 0; 333 | defaultConfigurationName = Release; 334 | }; 335 | 01AFFC1C202B106E00C3FC5B /* Build configuration list for PBXNativeTarget "StickerViewSample" */ = { 336 | isa = XCConfigurationList; 337 | buildConfigurations = ( 338 | 01AFFC1D202B106E00C3FC5B /* Debug */, 339 | 01AFFC1E202B106E00C3FC5B /* Release */, 340 | ); 341 | defaultConfigurationIsVisible = 0; 342 | defaultConfigurationName = Release; 343 | }; 344 | /* End XCConfigurationList section */ 345 | }; 346 | rootObject = 01AFFC02202B106E00C3FC5B /* Project object */; 347 | } 348 | -------------------------------------------------------------------------------- /StickerViewSample/StickerViewSample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /StickerViewSample/StickerViewSample.xcodeproj/project.xcworkspace/xcuserdata/leeliangliang.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/injap2017/StickerView/03c3254713c2cd2d63e72f6021264bade3d92716/StickerViewSample/StickerViewSample.xcodeproj/project.xcworkspace/xcuserdata/leeliangliang.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /StickerViewSample/StickerViewSample.xcodeproj/xcuserdata/leeliangliang.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | StickerViewSample.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /StickerViewSample/StickerViewSample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // StickerViewSample 4 | // 5 | // Created by Edgar Sia on 2/7/18. 6 | // Copyright © 2018 Edgar Sia. 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 | -------------------------------------------------------------------------------- /StickerViewSample/StickerViewSample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /StickerViewSample/StickerViewSample/Assets.xcassets/Close.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "close2@3x.png", 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 | } -------------------------------------------------------------------------------- /StickerViewSample/StickerViewSample/Assets.xcassets/Close.imageset/close2@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/injap2017/StickerView/03c3254713c2cd2d63e72f6021264bade3d92716/StickerViewSample/StickerViewSample/Assets.xcassets/Close.imageset/close2@3x.png -------------------------------------------------------------------------------- /StickerViewSample/StickerViewSample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /StickerViewSample/StickerViewSample/Assets.xcassets/Flip.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Flip.png", 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 | } -------------------------------------------------------------------------------- /StickerViewSample/StickerViewSample/Assets.xcassets/Flip.imageset/Flip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/injap2017/StickerView/03c3254713c2cd2d63e72f6021264bade3d92716/StickerViewSample/StickerViewSample/Assets.xcassets/Flip.imageset/Flip.png -------------------------------------------------------------------------------- /StickerViewSample/StickerViewSample/Assets.xcassets/HotDog.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "hot dog.png", 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 | } -------------------------------------------------------------------------------- /StickerViewSample/StickerViewSample/Assets.xcassets/HotDog.imageset/hot dog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/injap2017/StickerView/03c3254713c2cd2d63e72f6021264bade3d92716/StickerViewSample/StickerViewSample/Assets.xcassets/HotDog.imageset/hot dog.png -------------------------------------------------------------------------------- /StickerViewSample/StickerViewSample/Assets.xcassets/Rotate.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "RoundRotate@3x.png", 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 | } -------------------------------------------------------------------------------- /StickerViewSample/StickerViewSample/Assets.xcassets/Rotate.imageset/RoundRotate@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/injap2017/StickerView/03c3254713c2cd2d63e72f6021264bade3d92716/StickerViewSample/StickerViewSample/Assets.xcassets/Rotate.imageset/RoundRotate@3x.png -------------------------------------------------------------------------------- /StickerViewSample/StickerViewSample/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 | -------------------------------------------------------------------------------- /StickerViewSample/StickerViewSample/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 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /StickerViewSample/StickerViewSample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 32 | 33 | UIInterfaceOrientationPortrait 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /StickerViewSample/StickerViewSample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // StickerViewSample 4 | // 5 | // Created by Edgar Sia on 2/7/18. 6 | // Copyright © 2018 Edgar Sia. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | private var _selectedStickerView:StickerView? 14 | var selectedStickerView:StickerView? { 15 | get { 16 | return _selectedStickerView 17 | } 18 | set { 19 | 20 | // if other sticker choosed then resign the handler 21 | if _selectedStickerView != newValue { 22 | if let selectedStickerView = _selectedStickerView { 23 | selectedStickerView.showEditingHandlers = false 24 | } 25 | _selectedStickerView = newValue 26 | } 27 | 28 | // assign handler to new sticker added 29 | if let selectedStickerView = _selectedStickerView { 30 | selectedStickerView.showEditingHandlers = true 31 | selectedStickerView.superview?.bringSubview(toFront: selectedStickerView) 32 | } 33 | } 34 | } 35 | 36 | } 37 | 38 | // MARK: UIViewController cycle 39 | extension ViewController { 40 | override func viewDidLoad() { 41 | super.viewDidLoad() 42 | 43 | self.initialize() 44 | } 45 | } 46 | 47 | // MARK: Functions 48 | extension ViewController { 49 | func initialize() { 50 | 51 | // UIView as a container 52 | let testView = UIView.init(frame: CGRect.init(x: 0, y: 0, width: 150, height: 100)) 53 | testView.backgroundColor = UIColor.red 54 | 55 | let stickerView = StickerView.init(contentView: testView) 56 | stickerView.center = self.view.center 57 | stickerView.delegate = self 58 | stickerView.outlineBorderColor = UIColor.blue 59 | stickerView.setImage(UIImage.init(named: "Close")!, forHandler: StickerViewHandler.close) 60 | stickerView.setImage(UIImage.init(named: "Rotate")!, forHandler: StickerViewHandler.rotate) 61 | stickerView.setImage(UIImage.init(named: "Flip")!, forHandler: StickerViewHandler.flip) 62 | stickerView.setHandlerSize(40) 63 | self.view.addSubview(stickerView) 64 | 65 | // UILabel as a container 66 | let testLabel = UILabel.init(frame: CGRect.init(x: 0, y: 0, width: 100, height: 50)) 67 | testLabel.text = "Test Label" 68 | testLabel.textAlignment = .center 69 | 70 | let stickerView2 = StickerView.init(contentView: testLabel) 71 | stickerView2.center = CGPoint.init(x: 100, y: 100) 72 | stickerView2.delegate = self 73 | stickerView2.setImage(UIImage.init(named: "Close")!, forHandler: StickerViewHandler.close) 74 | stickerView2.setImage(UIImage.init(named: "Rotate")!, forHandler: StickerViewHandler.rotate) 75 | stickerView2.showEditingHandlers = false 76 | self.view.addSubview(stickerView2) 77 | 78 | // UIImageView as a container 79 | let testImage = UIImageView.init(frame: CGRect.init(x: 0, y: 0, width: 100, height: 50)) 80 | testImage.image = UIImage.init(named: "HotDog") 81 | 82 | let stickerView3 = StickerView.init(contentView: testImage) 83 | stickerView3.center = CGPoint.init(x: 150, y: 150) 84 | stickerView3.delegate = self 85 | stickerView3.setImage(UIImage.init(named: "Close")!, forHandler: StickerViewHandler.close) 86 | stickerView3.setImage(UIImage.init(named: "Rotate")!, forHandler: StickerViewHandler.rotate) 87 | stickerView3.setImage(UIImage.init(named: "Flip")!, forHandler: StickerViewHandler.flip) 88 | stickerView3.showEditingHandlers = false 89 | self.view.addSubview(stickerView3) 90 | 91 | // first off assign handler to stickerView 92 | self.selectedStickerView = stickerView 93 | } 94 | } 95 | 96 | // MARK: TapGestureRecognizer 97 | extension ViewController { 98 | @IBAction func tap(_ sender:UITapGestureRecognizer) { 99 | self.selectedStickerView?.showEditingHandlers = false 100 | } 101 | } 102 | 103 | // MARK: StickerViewDelegate 104 | extension ViewController: StickerViewDelegate { 105 | func stickerViewDidBeginMoving(_ stickerView: StickerView) { 106 | self.selectedStickerView = stickerView 107 | } 108 | 109 | func stickerViewDidChangeMoving(_ stickerView: StickerView) { 110 | 111 | } 112 | 113 | func stickerViewDidEndMoving(_ stickerView: StickerView) { 114 | 115 | } 116 | 117 | func stickerViewDidBeginRotating(_ stickerView: StickerView) { 118 | 119 | } 120 | 121 | func stickerViewDidChangeRotating(_ stickerView: StickerView) { 122 | 123 | } 124 | 125 | func stickerViewDidEndRotating(_ stickerView: StickerView) { 126 | 127 | } 128 | 129 | func stickerViewDidClose(_ stickerView: StickerView) { 130 | 131 | } 132 | 133 | func stickerViewDidTap(_ stickerView: StickerView) { 134 | self.selectedStickerView = stickerView 135 | } 136 | } 137 | --------------------------------------------------------------------------------