mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			196 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			196 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Swift
		
	
| //
 | |
| //  Copyright (c) 2018 Open Whisper Systems. All rights reserved.
 | |
| //
 | |
| 
 | |
| import UIKit
 | |
| 
 | |
| class ImageEditorGestureRecognizer: UIGestureRecognizer {
 | |
| 
 | |
|     @objc
 | |
|     public var shouldAllowOutsideView = true
 | |
| 
 | |
|     @objc
 | |
|     public weak var canvasView: UIView?
 | |
| 
 | |
|     @objc
 | |
|     override func canPrevent(_ preventedGestureRecognizer: UIGestureRecognizer) -> Bool {
 | |
|         return false
 | |
|     }
 | |
| 
 | |
|     @objc
 | |
|     override func canBePrevented(by: UIGestureRecognizer) -> Bool {
 | |
|         return false
 | |
|     }
 | |
| 
 | |
|     @objc
 | |
|     override func shouldRequireFailure(of: UIGestureRecognizer) -> Bool {
 | |
|         return false
 | |
|     }
 | |
| 
 | |
|     @objc
 | |
|     override func shouldBeRequiredToFail(by: UIGestureRecognizer) -> Bool {
 | |
|         return true
 | |
|     }
 | |
| 
 | |
|     // MARK: - Touch Handling
 | |
| 
 | |
|     @objc
 | |
|     override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
 | |
|         super.touchesBegan(touches, with: event)
 | |
| 
 | |
|         if state == .possible,
 | |
|             touchType(for: touches, with: event) == .valid {
 | |
|             // If a gesture starts with a valid touch, begin stroke.
 | |
|             state = .began
 | |
|         } else {
 | |
|             state = .failed
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     @objc
 | |
|     override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
 | |
|         super.touchesMoved(touches, with: event)
 | |
| 
 | |
|         switch state {
 | |
|         case .began, .changed:
 | |
|             switch touchType(for: touches, with: event) {
 | |
|             case .valid:
 | |
|                 // If a gesture continues with a valid touch, continue stroke.
 | |
|                 state = .changed
 | |
|             case .invalid:
 | |
|                 state = .failed
 | |
|             case .outside:
 | |
|                 // If a gesture continues with a valid touch _outside the canvas_,
 | |
|                 // end stroke.
 | |
|                 state = .ended
 | |
|             }
 | |
|         default:
 | |
|             state = .failed
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     @objc
 | |
|     override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
 | |
|         super.touchesEnded(touches, with: event)
 | |
| 
 | |
|         switch state {
 | |
|         case .began, .changed:
 | |
|             switch touchType(for: touches, with: event) {
 | |
|             case .valid, .outside:
 | |
|                 // If a gesture ends with a valid touch, end stroke.
 | |
|                 state = .ended
 | |
|             case .invalid:
 | |
|                 state = .failed
 | |
|             }
 | |
|         default:
 | |
|             state = .failed
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     @objc
 | |
|     override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
 | |
|         super.touchesCancelled(touches, with: event)
 | |
| 
 | |
|         state = .cancelled
 | |
|     }
 | |
| 
 | |
|     public enum TouchType {
 | |
|         case invalid
 | |
|         case valid
 | |
|         case outside
 | |
|     }
 | |
| 
 | |
|     private func touchType(for touches: Set<UITouch>, with event: UIEvent) -> TouchType {
 | |
|         guard let gestureView = self.view else {
 | |
|             owsFailDebug("Missing gestureView")
 | |
|             return .invalid
 | |
|         }
 | |
|         guard let canvasView = canvasView else {
 | |
|             owsFailDebug("Missing canvasView")
 | |
|             return .invalid
 | |
|         }
 | |
|         guard let allTouches = event.allTouches else {
 | |
|             owsFailDebug("Missing allTouches")
 | |
|             return .invalid
 | |
|         }
 | |
|         guard allTouches.count <= 1 else {
 | |
|             return .invalid
 | |
|         }
 | |
|         guard touches.count == 1 else {
 | |
|             return .invalid
 | |
|         }
 | |
|         guard let firstTouch: UITouch = touches.first else {
 | |
|             return .invalid
 | |
|         }
 | |
| 
 | |
|         let isNewTouch = firstTouch.phase == .began
 | |
|         if isNewTouch {
 | |
|             // Reject new touches that are inside a control subview.
 | |
|             if subviewControl(ofView: gestureView, contains: firstTouch) {
 | |
|                 return .invalid
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // Reject new touches outside this GR's view's bounds.
 | |
|         let location = firstTouch.location(in: canvasView)
 | |
|         if !canvasView.bounds.contains(location) {
 | |
|             if shouldAllowOutsideView {
 | |
|                 // Do nothing
 | |
|             } else if isNewTouch {
 | |
|                 return .invalid
 | |
|             } else {
 | |
|                 return .outside
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if isNewTouch {
 | |
|             // Ignore touches that start near the top or bottom edge of the screen;
 | |
|             // they may be a system edge swipe gesture.
 | |
|             let rootView = self.rootView(of: gestureView)
 | |
|             let rootLocation = firstTouch.location(in: rootView)
 | |
|             let distanceToTopEdge = max(0, rootLocation.y)
 | |
|             let distanceToBottomEdge = max(0, rootView.bounds.size.height - rootLocation.y)
 | |
|             let distanceToNearestEdge = min(distanceToTopEdge, distanceToBottomEdge)
 | |
|             let kSystemEdgeSwipeTolerance: CGFloat = 50
 | |
|             if (distanceToNearestEdge < kSystemEdgeSwipeTolerance) {
 | |
|                 return .invalid
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return .valid
 | |
|     }
 | |
| 
 | |
|     private func subviewControl(ofView superview: UIView, contains touch: UITouch) -> Bool {
 | |
|         for subview in superview.subviews {
 | |
|             guard !subview.isHidden, subview.isUserInteractionEnabled else {
 | |
|                 continue
 | |
|             }
 | |
|             let location = touch.location(in: subview)
 | |
|             guard subview.bounds.contains(location) else {
 | |
|                 continue
 | |
|             }
 | |
|             if subview as? UIControl != nil {
 | |
|                 return true
 | |
|             }
 | |
|             if subviewControl(ofView: subview, contains: touch) {
 | |
|                 return true
 | |
|             }
 | |
|         }
 | |
|         return false
 | |
|     }
 | |
| 
 | |
|     private func rootView(of view: UIView) -> UIView {
 | |
|         var responder: UIResponder? = view
 | |
|         var lastView: UIView = view
 | |
|         while true {
 | |
|             guard let currentResponder = responder else {
 | |
|                 return lastView
 | |
|             }
 | |
|             if let currentView = currentResponder as? UIView {
 | |
|                 lastView = currentView
 | |
|             }
 | |
|             responder = currentResponder.next
 | |
|         }
 | |
|     }
 | |
| }
 |