Adding dragging ability to a UIView using protocols
Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
3
down vote
favorite
I recently published an iOS control/component called BJDraggable
which basically, with a call of a method, enables us to drag a view within its superview boundary. The whole setup works using the UIKitDynamics
API. (Scroll to last to see the output achieved.)
These are the methods I expose to consumers. You could follow up from these method calls in the detailed code (BJDraggable.swift).
@objc protocol BJDraggable: class
@objc func addDraggability(withinView referenceView: UIView)
@objc func addDraggability(withinView referenceView: UIView, withMargin insets:UIEdgeInsets)
@objc func removeDraggability()
Here is my full code. Please bear with the length of the code; it is pretty long. Demo project is available here at GitHub. Thanks in advance for your time.
BJDraggable.swift
import UIKit
var kReferenceViewKey: String = "ReferenceViewKey"
var kDynamicAnimatorKey: String = "DynamicAnimatorKey"
var kAttachmentBehaviourKey: String = "AttachmentBehaviourKey"
var kPanGestureKey: String = "PanGestureKey"
var kResetPositionKey: String = "ResetPositionKey"
fileprivate enum BehaviourNames
case main
case border
case collision
case attachment
/**A simple protocol *(No need to implement methods and properties yourself. Just drop-in the BJDraggable file to your project and all done)* utilizing the powerful `UIKitDynamics` API, which makes **ANY** `UIView` draggable within a boundary view that acts as collision body, with a single method call.
*/
@objc protocol BJDraggable: class
/**
Gives you the power to drag your `UIView` anywhere within a specified view, and collide within its bounds.
- parameter referenceView: The boundary view which acts as a wall, and your view will collide with it and would never fall out of bounds hopefully. **Note that the reference view should contain the view that you're trying to add draggability to in its view hierarchy. The app would crash otherwise.**
*/
@objc func addDraggability(withinView referenceView: UIView)
/**
This single method call will give you the power to drag your `UIView` anywhere within a specified view, and collide within its bounds.
- parameter referenceView: This is the boundary view which acts as a wall, and your view will collide with it and would never fall out of bounds hopefully. **Note that the reference view should contain the view that you're trying to add draggability to in its view hierarchy. The app would crash otherwise.**
- parameter insets: If you want to make the boundary to be offset positively or negatively, you can specify that here. This is nothing but a margin for the boundary.
*/
@objc func addDraggability(withinView referenceView: UIView, withMargin insets:UIEdgeInsets)
/**
Removes the power from you, to drag the view in question
*/
@objc func removeDraggability()
///Implementation of `BJDraggable` protocol
extension UIView: BJDraggable
//
//////////////////////////////////////////////////////////////////////////////////////////
//MARK:-
//MARK: Properties
//MARK:-
//////////////////////////////////////////////////////////////////////////////////////////
//
public var shouldResetViewPositionAfterRemovingDraggability: Bool
get
let getValue = (objc_getAssociatedObject(self, &kResetPositionKey) as? Bool)
return getValue == nil ? false : getValue!
set
objc_setAssociatedObject(self, &kResetPositionKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
self.translatesAutoresizingMaskIntoConstraints = !newValue
fileprivate var referenceView: UIView?
get
return objc_getAssociatedObject(self, &kReferenceViewKey) as? UIView
set
objc_setAssociatedObject(self, &kReferenceViewKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
fileprivate var animator: UIDynamicAnimator?
get
return objc_getAssociatedObject(self, &kDynamicAnimatorKey) as? UIDynamicAnimator
set
objc_setAssociatedObject(self, &kDynamicAnimatorKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
fileprivate var attachmentBehaviour: UIAttachmentBehavior?
get
return objc_getAssociatedObject(self, &kAttachmentBehaviourKey) as? UIAttachmentBehavior
set
objc_setAssociatedObject(self, &kAttachmentBehaviourKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
fileprivate var panGestureRecognizer: UIPanGestureRecognizer?
get
return objc_getAssociatedObject(self, &kPanGestureKey) as? UIPanGestureRecognizer
set
objc_setAssociatedObject(self, &kPanGestureKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
//
//////////////////////////////////////////////////////////////////////////////////////////
//MARK:-
//MARK: Method Implementations
//MARK:-
//////////////////////////////////////////////////////////////////////////////////////////
//
final func addDraggability(withinView referenceView: UIView)
self.addDraggability(withinView: referenceView, withMargin: UIEdgeInsets.init(top: 0, left: 0, bottom: 0, right: 0))
final func addDraggability(withinView referenceView: UIView, withMargin insets:UIEdgeInsets)
guard self.animator == nil else return
///////////////////////
/////Configuration/////
///////////////////////
performInitialConfiguration()
addPanGestureRecognizer()
////////////////////////////////////////////////
/////Getting Collision Items For Behaviours/////
////////////////////////////////////////////////
let collisionItems = self.drawAndGetCollisionViewsAround(referenceView, withInsets: insets)
////////////////////
/////Behaviours/////
////////////////////
let mainItemBehaviour = get(behaviour: .main, for: referenceView, withInsets: insets, configuredWith: collisionItems)!
let borderItemsBehaviour = get(behaviour: .border, for: referenceView, withInsets: insets, configuredWith: collisionItems)!
let collisionBehaviour = get(behaviour: .collision, for: referenceView, withInsets: insets, configuredWith: collisionItems)!
let attachmentBehaviour = get(behaviour: .attachment, for: referenceView, withInsets: insets, configuredWith: collisionItems)!
//////////////////
/////Animator/////
//////////////////
let animator = UIDynamicAnimator.init(referenceView: referenceView)
animator.addBehavior(mainItemBehaviour)
animator.addBehavior(borderItemsBehaviour)
animator.addBehavior(collisionBehaviour)
animator.addBehavior(attachmentBehaviour)
/////////////////////
/////Persistence/////
/////////////////////
self.animator = animator
self.referenceView = referenceView
self.attachmentBehaviour = attachmentBehaviour as? UIAttachmentBehavior
final func removeDraggability()
if let recognizer = self.panGestureRecognizer self.removeGestureRecognizer(recognizer)
self.translatesAutoresizingMaskIntoConstraints = !self.shouldResetViewPositionAfterRemovingDraggability
self.animator?.removeAllBehaviors()
if let subviews = self.referenceView?.subviews
for view in subviews
self.referenceView = nil
self.attachmentBehaviour = nil
self.animator = nil
self.panGestureRecognizer = nil
//
//////////////////////////////////////////////////////////////////////////////////////////
//MARK:-
//MARK: Helpers 1
//MARK:-
//////////////////////////////////////////////////////////////////////////////////////////
//
fileprivate func performInitialConfiguration()
self.isUserInteractionEnabled = true
fileprivate func addPanGestureRecognizer()
let panGestureRecognizer = UIPanGestureRecognizer.init(target: self, action: #selector(self.panGestureHandler(_:)))
self.addGestureRecognizer(panGestureRecognizer)
self.panGestureRecognizer = panGestureRecognizer
@objc final func panGestureHandler(_ gesture: UIPanGestureRecognizer)
guard let referenceView = self.referenceView else return
let touchPoint = gesture.location(in: referenceView)
self.attachmentBehaviour?.anchorPoint = touchPoint
fileprivate func get(behaviour:BehaviourNames, for referenceView:UIView, withInsets:UIEdgeInsets, configuredWith boundaryCollisionItems:[UIDynamicItem]) -> UIDynamicBehavior?
let allItems = [self] + boundaryCollisionItems
switch behaviour
case .border:
let borderItemsBehaviour = UIDynamicItemBehavior.init(items: boundaryCollisionItems)
borderItemsBehaviour.allowsRotation = false
borderItemsBehaviour.isAnchored = true
borderItemsBehaviour.friction = 2.0
return borderItemsBehaviour
case .main:
let mainItemBehaviour = UIDynamicItemBehavior.init(items: [self])
mainItemBehaviour.allowsRotation = false
mainItemBehaviour.isAnchored = false
mainItemBehaviour.friction = 2.0
return mainItemBehaviour
case .collision:
let collisionBehaviour = UICollisionBehavior.init(items: allItems)
collisionBehaviour.collisionMode = .items
collisionBehaviour.addBoundary(withIdentifier: "Boundary" as NSCopying, for: self.boundaryPathFor(referenceView))
return collisionBehaviour
case .attachment:
let attachmentBehaviour = UIAttachmentBehavior.init(item: self, attachedToAnchor: self.center)
return attachmentBehaviour
//
//////////////////////////////////////////////////////////////////////////////////////////
//MARK:-
//MARK: Helpers 2
//MARK:-
//////////////////////////////////////////////////////////////////////////////////////////
//
func alteredFrameByPoints(_ point:CGFloat) -> CGRect
var newFrame = self.frame
newFrame.origin.x -= point
newFrame.origin.y -= point
newFrame.size.width += point * 2
newFrame.size.height += point * 2
return newFrame
fileprivate func boundaryPathFor(_ view:UIView) -> UIBezierPath
let cgPath = CGPath.init(rect: view.alteredFrameByPoints(2.0), transform:nil)
return UIBezierPath.init(cgPath: cgPath)
fileprivate func getNewRectFrom(rect:CGRect, byApplying insets:UIEdgeInsets) -> CGRect
var newRect:CGRect = .zero
let x = rect.origin.x + insets.left
let y = rect.origin.y + insets.top
let width = rect.width - insets.right
let height = rect.height - insets.bottom
newRect.origin.x = x
newRect.origin.y = y
newRect.size.width = width
newRect.size.height = height
return newRect
@discardableResult
fileprivate func drawAndGetCollisionViewsAround(_ referenceView:UIView, withInsets insets:UIEdgeInsets) -> ([UIView])
let boundaryViewWidth = CGFloat(1)
let boundaryViewHeight = CGFloat(1)
////////////////////
////Get New Rect////
////////////////////
let newReferenceViewRect = self.getNewRectFrom(rect:referenceView.alteredFrameByPoints(1),
byApplying:insets)
////////////
////Left////
////////////
let leftView = UIView(frame: CGRect.init(x: newReferenceViewRect.origin.x - (boundaryViewWidth - 1), y: newReferenceViewRect.origin.y, width: boundaryViewWidth, height: newReferenceViewRect.size.height - insets.bottom))
leftView.isUserInteractionEnabled = false
leftView.tag = 122
/////////////
////Right////
/////////////
let rightView = UIView(frame: CGRect.init(x: newReferenceViewRect.size.width - 2.0, y: newReferenceViewRect.origin.y, width: boundaryViewWidth, height: newReferenceViewRect.size.height - insets.bottom))
rightView.isUserInteractionEnabled = false
rightView.tag = 222
///////////
////Top////
///////////
let topView = UIView(frame: CGRect.init(x: newReferenceViewRect.origin.x, y: newReferenceViewRect.origin.y - (boundaryViewHeight - 1), width: newReferenceViewRect.size.width - insets.right, height: boundaryViewHeight))
topView.isUserInteractionEnabled = false
topView.tag = 322
//////////////
////Bottom////
//////////////
let bottomView = UIView(frame: CGRect.init(x: newReferenceViewRect.origin.x, y: newReferenceViewRect.size.height - 2.0, width: newReferenceViewRect.size.width - insets.right, height: boundaryViewHeight))
bottomView.isUserInteractionEnabled = false
bottomView.tag = 422
///////////////////
////Add Subview////
///////////////////
referenceView.addSubview(leftView)
referenceView.addSubview(rightView)
referenceView.addSubview(topView)
referenceView.addSubview(bottomView)
return [leftView, rightView, topView, bottomView]
This is how it works:
swift ios protocols uikit
add a comment |Â
up vote
3
down vote
favorite
I recently published an iOS control/component called BJDraggable
which basically, with a call of a method, enables us to drag a view within its superview boundary. The whole setup works using the UIKitDynamics
API. (Scroll to last to see the output achieved.)
These are the methods I expose to consumers. You could follow up from these method calls in the detailed code (BJDraggable.swift).
@objc protocol BJDraggable: class
@objc func addDraggability(withinView referenceView: UIView)
@objc func addDraggability(withinView referenceView: UIView, withMargin insets:UIEdgeInsets)
@objc func removeDraggability()
Here is my full code. Please bear with the length of the code; it is pretty long. Demo project is available here at GitHub. Thanks in advance for your time.
BJDraggable.swift
import UIKit
var kReferenceViewKey: String = "ReferenceViewKey"
var kDynamicAnimatorKey: String = "DynamicAnimatorKey"
var kAttachmentBehaviourKey: String = "AttachmentBehaviourKey"
var kPanGestureKey: String = "PanGestureKey"
var kResetPositionKey: String = "ResetPositionKey"
fileprivate enum BehaviourNames
case main
case border
case collision
case attachment
/**A simple protocol *(No need to implement methods and properties yourself. Just drop-in the BJDraggable file to your project and all done)* utilizing the powerful `UIKitDynamics` API, which makes **ANY** `UIView` draggable within a boundary view that acts as collision body, with a single method call.
*/
@objc protocol BJDraggable: class
/**
Gives you the power to drag your `UIView` anywhere within a specified view, and collide within its bounds.
- parameter referenceView: The boundary view which acts as a wall, and your view will collide with it and would never fall out of bounds hopefully. **Note that the reference view should contain the view that you're trying to add draggability to in its view hierarchy. The app would crash otherwise.**
*/
@objc func addDraggability(withinView referenceView: UIView)
/**
This single method call will give you the power to drag your `UIView` anywhere within a specified view, and collide within its bounds.
- parameter referenceView: This is the boundary view which acts as a wall, and your view will collide with it and would never fall out of bounds hopefully. **Note that the reference view should contain the view that you're trying to add draggability to in its view hierarchy. The app would crash otherwise.**
- parameter insets: If you want to make the boundary to be offset positively or negatively, you can specify that here. This is nothing but a margin for the boundary.
*/
@objc func addDraggability(withinView referenceView: UIView, withMargin insets:UIEdgeInsets)
/**
Removes the power from you, to drag the view in question
*/
@objc func removeDraggability()
///Implementation of `BJDraggable` protocol
extension UIView: BJDraggable
//
//////////////////////////////////////////////////////////////////////////////////////////
//MARK:-
//MARK: Properties
//MARK:-
//////////////////////////////////////////////////////////////////////////////////////////
//
public var shouldResetViewPositionAfterRemovingDraggability: Bool
get
let getValue = (objc_getAssociatedObject(self, &kResetPositionKey) as? Bool)
return getValue == nil ? false : getValue!
set
objc_setAssociatedObject(self, &kResetPositionKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
self.translatesAutoresizingMaskIntoConstraints = !newValue
fileprivate var referenceView: UIView?
get
return objc_getAssociatedObject(self, &kReferenceViewKey) as? UIView
set
objc_setAssociatedObject(self, &kReferenceViewKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
fileprivate var animator: UIDynamicAnimator?
get
return objc_getAssociatedObject(self, &kDynamicAnimatorKey) as? UIDynamicAnimator
set
objc_setAssociatedObject(self, &kDynamicAnimatorKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
fileprivate var attachmentBehaviour: UIAttachmentBehavior?
get
return objc_getAssociatedObject(self, &kAttachmentBehaviourKey) as? UIAttachmentBehavior
set
objc_setAssociatedObject(self, &kAttachmentBehaviourKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
fileprivate var panGestureRecognizer: UIPanGestureRecognizer?
get
return objc_getAssociatedObject(self, &kPanGestureKey) as? UIPanGestureRecognizer
set
objc_setAssociatedObject(self, &kPanGestureKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
//
//////////////////////////////////////////////////////////////////////////////////////////
//MARK:-
//MARK: Method Implementations
//MARK:-
//////////////////////////////////////////////////////////////////////////////////////////
//
final func addDraggability(withinView referenceView: UIView)
self.addDraggability(withinView: referenceView, withMargin: UIEdgeInsets.init(top: 0, left: 0, bottom: 0, right: 0))
final func addDraggability(withinView referenceView: UIView, withMargin insets:UIEdgeInsets)
guard self.animator == nil else return
///////////////////////
/////Configuration/////
///////////////////////
performInitialConfiguration()
addPanGestureRecognizer()
////////////////////////////////////////////////
/////Getting Collision Items For Behaviours/////
////////////////////////////////////////////////
let collisionItems = self.drawAndGetCollisionViewsAround(referenceView, withInsets: insets)
////////////////////
/////Behaviours/////
////////////////////
let mainItemBehaviour = get(behaviour: .main, for: referenceView, withInsets: insets, configuredWith: collisionItems)!
let borderItemsBehaviour = get(behaviour: .border, for: referenceView, withInsets: insets, configuredWith: collisionItems)!
let collisionBehaviour = get(behaviour: .collision, for: referenceView, withInsets: insets, configuredWith: collisionItems)!
let attachmentBehaviour = get(behaviour: .attachment, for: referenceView, withInsets: insets, configuredWith: collisionItems)!
//////////////////
/////Animator/////
//////////////////
let animator = UIDynamicAnimator.init(referenceView: referenceView)
animator.addBehavior(mainItemBehaviour)
animator.addBehavior(borderItemsBehaviour)
animator.addBehavior(collisionBehaviour)
animator.addBehavior(attachmentBehaviour)
/////////////////////
/////Persistence/////
/////////////////////
self.animator = animator
self.referenceView = referenceView
self.attachmentBehaviour = attachmentBehaviour as? UIAttachmentBehavior
final func removeDraggability()
if let recognizer = self.panGestureRecognizer self.removeGestureRecognizer(recognizer)
self.translatesAutoresizingMaskIntoConstraints = !self.shouldResetViewPositionAfterRemovingDraggability
self.animator?.removeAllBehaviors()
if let subviews = self.referenceView?.subviews
for view in subviews
self.referenceView = nil
self.attachmentBehaviour = nil
self.animator = nil
self.panGestureRecognizer = nil
//
//////////////////////////////////////////////////////////////////////////////////////////
//MARK:-
//MARK: Helpers 1
//MARK:-
//////////////////////////////////////////////////////////////////////////////////////////
//
fileprivate func performInitialConfiguration()
self.isUserInteractionEnabled = true
fileprivate func addPanGestureRecognizer()
let panGestureRecognizer = UIPanGestureRecognizer.init(target: self, action: #selector(self.panGestureHandler(_:)))
self.addGestureRecognizer(panGestureRecognizer)
self.panGestureRecognizer = panGestureRecognizer
@objc final func panGestureHandler(_ gesture: UIPanGestureRecognizer)
guard let referenceView = self.referenceView else return
let touchPoint = gesture.location(in: referenceView)
self.attachmentBehaviour?.anchorPoint = touchPoint
fileprivate func get(behaviour:BehaviourNames, for referenceView:UIView, withInsets:UIEdgeInsets, configuredWith boundaryCollisionItems:[UIDynamicItem]) -> UIDynamicBehavior?
let allItems = [self] + boundaryCollisionItems
switch behaviour
case .border:
let borderItemsBehaviour = UIDynamicItemBehavior.init(items: boundaryCollisionItems)
borderItemsBehaviour.allowsRotation = false
borderItemsBehaviour.isAnchored = true
borderItemsBehaviour.friction = 2.0
return borderItemsBehaviour
case .main:
let mainItemBehaviour = UIDynamicItemBehavior.init(items: [self])
mainItemBehaviour.allowsRotation = false
mainItemBehaviour.isAnchored = false
mainItemBehaviour.friction = 2.0
return mainItemBehaviour
case .collision:
let collisionBehaviour = UICollisionBehavior.init(items: allItems)
collisionBehaviour.collisionMode = .items
collisionBehaviour.addBoundary(withIdentifier: "Boundary" as NSCopying, for: self.boundaryPathFor(referenceView))
return collisionBehaviour
case .attachment:
let attachmentBehaviour = UIAttachmentBehavior.init(item: self, attachedToAnchor: self.center)
return attachmentBehaviour
//
//////////////////////////////////////////////////////////////////////////////////////////
//MARK:-
//MARK: Helpers 2
//MARK:-
//////////////////////////////////////////////////////////////////////////////////////////
//
func alteredFrameByPoints(_ point:CGFloat) -> CGRect
var newFrame = self.frame
newFrame.origin.x -= point
newFrame.origin.y -= point
newFrame.size.width += point * 2
newFrame.size.height += point * 2
return newFrame
fileprivate func boundaryPathFor(_ view:UIView) -> UIBezierPath
let cgPath = CGPath.init(rect: view.alteredFrameByPoints(2.0), transform:nil)
return UIBezierPath.init(cgPath: cgPath)
fileprivate func getNewRectFrom(rect:CGRect, byApplying insets:UIEdgeInsets) -> CGRect
var newRect:CGRect = .zero
let x = rect.origin.x + insets.left
let y = rect.origin.y + insets.top
let width = rect.width - insets.right
let height = rect.height - insets.bottom
newRect.origin.x = x
newRect.origin.y = y
newRect.size.width = width
newRect.size.height = height
return newRect
@discardableResult
fileprivate func drawAndGetCollisionViewsAround(_ referenceView:UIView, withInsets insets:UIEdgeInsets) -> ([UIView])
let boundaryViewWidth = CGFloat(1)
let boundaryViewHeight = CGFloat(1)
////////////////////
////Get New Rect////
////////////////////
let newReferenceViewRect = self.getNewRectFrom(rect:referenceView.alteredFrameByPoints(1),
byApplying:insets)
////////////
////Left////
////////////
let leftView = UIView(frame: CGRect.init(x: newReferenceViewRect.origin.x - (boundaryViewWidth - 1), y: newReferenceViewRect.origin.y, width: boundaryViewWidth, height: newReferenceViewRect.size.height - insets.bottom))
leftView.isUserInteractionEnabled = false
leftView.tag = 122
/////////////
////Right////
/////////////
let rightView = UIView(frame: CGRect.init(x: newReferenceViewRect.size.width - 2.0, y: newReferenceViewRect.origin.y, width: boundaryViewWidth, height: newReferenceViewRect.size.height - insets.bottom))
rightView.isUserInteractionEnabled = false
rightView.tag = 222
///////////
////Top////
///////////
let topView = UIView(frame: CGRect.init(x: newReferenceViewRect.origin.x, y: newReferenceViewRect.origin.y - (boundaryViewHeight - 1), width: newReferenceViewRect.size.width - insets.right, height: boundaryViewHeight))
topView.isUserInteractionEnabled = false
topView.tag = 322
//////////////
////Bottom////
//////////////
let bottomView = UIView(frame: CGRect.init(x: newReferenceViewRect.origin.x, y: newReferenceViewRect.size.height - 2.0, width: newReferenceViewRect.size.width - insets.right, height: boundaryViewHeight))
bottomView.isUserInteractionEnabled = false
bottomView.tag = 422
///////////////////
////Add Subview////
///////////////////
referenceView.addSubview(leftView)
referenceView.addSubview(rightView)
referenceView.addSubview(topView)
referenceView.addSubview(bottomView)
return [leftView, rightView, topView, bottomView]
This is how it works:
swift ios protocols uikit
add a comment |Â
up vote
3
down vote
favorite
up vote
3
down vote
favorite
I recently published an iOS control/component called BJDraggable
which basically, with a call of a method, enables us to drag a view within its superview boundary. The whole setup works using the UIKitDynamics
API. (Scroll to last to see the output achieved.)
These are the methods I expose to consumers. You could follow up from these method calls in the detailed code (BJDraggable.swift).
@objc protocol BJDraggable: class
@objc func addDraggability(withinView referenceView: UIView)
@objc func addDraggability(withinView referenceView: UIView, withMargin insets:UIEdgeInsets)
@objc func removeDraggability()
Here is my full code. Please bear with the length of the code; it is pretty long. Demo project is available here at GitHub. Thanks in advance for your time.
BJDraggable.swift
import UIKit
var kReferenceViewKey: String = "ReferenceViewKey"
var kDynamicAnimatorKey: String = "DynamicAnimatorKey"
var kAttachmentBehaviourKey: String = "AttachmentBehaviourKey"
var kPanGestureKey: String = "PanGestureKey"
var kResetPositionKey: String = "ResetPositionKey"
fileprivate enum BehaviourNames
case main
case border
case collision
case attachment
/**A simple protocol *(No need to implement methods and properties yourself. Just drop-in the BJDraggable file to your project and all done)* utilizing the powerful `UIKitDynamics` API, which makes **ANY** `UIView` draggable within a boundary view that acts as collision body, with a single method call.
*/
@objc protocol BJDraggable: class
/**
Gives you the power to drag your `UIView` anywhere within a specified view, and collide within its bounds.
- parameter referenceView: The boundary view which acts as a wall, and your view will collide with it and would never fall out of bounds hopefully. **Note that the reference view should contain the view that you're trying to add draggability to in its view hierarchy. The app would crash otherwise.**
*/
@objc func addDraggability(withinView referenceView: UIView)
/**
This single method call will give you the power to drag your `UIView` anywhere within a specified view, and collide within its bounds.
- parameter referenceView: This is the boundary view which acts as a wall, and your view will collide with it and would never fall out of bounds hopefully. **Note that the reference view should contain the view that you're trying to add draggability to in its view hierarchy. The app would crash otherwise.**
- parameter insets: If you want to make the boundary to be offset positively or negatively, you can specify that here. This is nothing but a margin for the boundary.
*/
@objc func addDraggability(withinView referenceView: UIView, withMargin insets:UIEdgeInsets)
/**
Removes the power from you, to drag the view in question
*/
@objc func removeDraggability()
///Implementation of `BJDraggable` protocol
extension UIView: BJDraggable
//
//////////////////////////////////////////////////////////////////////////////////////////
//MARK:-
//MARK: Properties
//MARK:-
//////////////////////////////////////////////////////////////////////////////////////////
//
public var shouldResetViewPositionAfterRemovingDraggability: Bool
get
let getValue = (objc_getAssociatedObject(self, &kResetPositionKey) as? Bool)
return getValue == nil ? false : getValue!
set
objc_setAssociatedObject(self, &kResetPositionKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
self.translatesAutoresizingMaskIntoConstraints = !newValue
fileprivate var referenceView: UIView?
get
return objc_getAssociatedObject(self, &kReferenceViewKey) as? UIView
set
objc_setAssociatedObject(self, &kReferenceViewKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
fileprivate var animator: UIDynamicAnimator?
get
return objc_getAssociatedObject(self, &kDynamicAnimatorKey) as? UIDynamicAnimator
set
objc_setAssociatedObject(self, &kDynamicAnimatorKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
fileprivate var attachmentBehaviour: UIAttachmentBehavior?
get
return objc_getAssociatedObject(self, &kAttachmentBehaviourKey) as? UIAttachmentBehavior
set
objc_setAssociatedObject(self, &kAttachmentBehaviourKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
fileprivate var panGestureRecognizer: UIPanGestureRecognizer?
get
return objc_getAssociatedObject(self, &kPanGestureKey) as? UIPanGestureRecognizer
set
objc_setAssociatedObject(self, &kPanGestureKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
//
//////////////////////////////////////////////////////////////////////////////////////////
//MARK:-
//MARK: Method Implementations
//MARK:-
//////////////////////////////////////////////////////////////////////////////////////////
//
final func addDraggability(withinView referenceView: UIView)
self.addDraggability(withinView: referenceView, withMargin: UIEdgeInsets.init(top: 0, left: 0, bottom: 0, right: 0))
final func addDraggability(withinView referenceView: UIView, withMargin insets:UIEdgeInsets)
guard self.animator == nil else return
///////////////////////
/////Configuration/////
///////////////////////
performInitialConfiguration()
addPanGestureRecognizer()
////////////////////////////////////////////////
/////Getting Collision Items For Behaviours/////
////////////////////////////////////////////////
let collisionItems = self.drawAndGetCollisionViewsAround(referenceView, withInsets: insets)
////////////////////
/////Behaviours/////
////////////////////
let mainItemBehaviour = get(behaviour: .main, for: referenceView, withInsets: insets, configuredWith: collisionItems)!
let borderItemsBehaviour = get(behaviour: .border, for: referenceView, withInsets: insets, configuredWith: collisionItems)!
let collisionBehaviour = get(behaviour: .collision, for: referenceView, withInsets: insets, configuredWith: collisionItems)!
let attachmentBehaviour = get(behaviour: .attachment, for: referenceView, withInsets: insets, configuredWith: collisionItems)!
//////////////////
/////Animator/////
//////////////////
let animator = UIDynamicAnimator.init(referenceView: referenceView)
animator.addBehavior(mainItemBehaviour)
animator.addBehavior(borderItemsBehaviour)
animator.addBehavior(collisionBehaviour)
animator.addBehavior(attachmentBehaviour)
/////////////////////
/////Persistence/////
/////////////////////
self.animator = animator
self.referenceView = referenceView
self.attachmentBehaviour = attachmentBehaviour as? UIAttachmentBehavior
final func removeDraggability()
if let recognizer = self.panGestureRecognizer self.removeGestureRecognizer(recognizer)
self.translatesAutoresizingMaskIntoConstraints = !self.shouldResetViewPositionAfterRemovingDraggability
self.animator?.removeAllBehaviors()
if let subviews = self.referenceView?.subviews
for view in subviews
self.referenceView = nil
self.attachmentBehaviour = nil
self.animator = nil
self.panGestureRecognizer = nil
//
//////////////////////////////////////////////////////////////////////////////////////////
//MARK:-
//MARK: Helpers 1
//MARK:-
//////////////////////////////////////////////////////////////////////////////////////////
//
fileprivate func performInitialConfiguration()
self.isUserInteractionEnabled = true
fileprivate func addPanGestureRecognizer()
let panGestureRecognizer = UIPanGestureRecognizer.init(target: self, action: #selector(self.panGestureHandler(_:)))
self.addGestureRecognizer(panGestureRecognizer)
self.panGestureRecognizer = panGestureRecognizer
@objc final func panGestureHandler(_ gesture: UIPanGestureRecognizer)
guard let referenceView = self.referenceView else return
let touchPoint = gesture.location(in: referenceView)
self.attachmentBehaviour?.anchorPoint = touchPoint
fileprivate func get(behaviour:BehaviourNames, for referenceView:UIView, withInsets:UIEdgeInsets, configuredWith boundaryCollisionItems:[UIDynamicItem]) -> UIDynamicBehavior?
let allItems = [self] + boundaryCollisionItems
switch behaviour
case .border:
let borderItemsBehaviour = UIDynamicItemBehavior.init(items: boundaryCollisionItems)
borderItemsBehaviour.allowsRotation = false
borderItemsBehaviour.isAnchored = true
borderItemsBehaviour.friction = 2.0
return borderItemsBehaviour
case .main:
let mainItemBehaviour = UIDynamicItemBehavior.init(items: [self])
mainItemBehaviour.allowsRotation = false
mainItemBehaviour.isAnchored = false
mainItemBehaviour.friction = 2.0
return mainItemBehaviour
case .collision:
let collisionBehaviour = UICollisionBehavior.init(items: allItems)
collisionBehaviour.collisionMode = .items
collisionBehaviour.addBoundary(withIdentifier: "Boundary" as NSCopying, for: self.boundaryPathFor(referenceView))
return collisionBehaviour
case .attachment:
let attachmentBehaviour = UIAttachmentBehavior.init(item: self, attachedToAnchor: self.center)
return attachmentBehaviour
//
//////////////////////////////////////////////////////////////////////////////////////////
//MARK:-
//MARK: Helpers 2
//MARK:-
//////////////////////////////////////////////////////////////////////////////////////////
//
func alteredFrameByPoints(_ point:CGFloat) -> CGRect
var newFrame = self.frame
newFrame.origin.x -= point
newFrame.origin.y -= point
newFrame.size.width += point * 2
newFrame.size.height += point * 2
return newFrame
fileprivate func boundaryPathFor(_ view:UIView) -> UIBezierPath
let cgPath = CGPath.init(rect: view.alteredFrameByPoints(2.0), transform:nil)
return UIBezierPath.init(cgPath: cgPath)
fileprivate func getNewRectFrom(rect:CGRect, byApplying insets:UIEdgeInsets) -> CGRect
var newRect:CGRect = .zero
let x = rect.origin.x + insets.left
let y = rect.origin.y + insets.top
let width = rect.width - insets.right
let height = rect.height - insets.bottom
newRect.origin.x = x
newRect.origin.y = y
newRect.size.width = width
newRect.size.height = height
return newRect
@discardableResult
fileprivate func drawAndGetCollisionViewsAround(_ referenceView:UIView, withInsets insets:UIEdgeInsets) -> ([UIView])
let boundaryViewWidth = CGFloat(1)
let boundaryViewHeight = CGFloat(1)
////////////////////
////Get New Rect////
////////////////////
let newReferenceViewRect = self.getNewRectFrom(rect:referenceView.alteredFrameByPoints(1),
byApplying:insets)
////////////
////Left////
////////////
let leftView = UIView(frame: CGRect.init(x: newReferenceViewRect.origin.x - (boundaryViewWidth - 1), y: newReferenceViewRect.origin.y, width: boundaryViewWidth, height: newReferenceViewRect.size.height - insets.bottom))
leftView.isUserInteractionEnabled = false
leftView.tag = 122
/////////////
////Right////
/////////////
let rightView = UIView(frame: CGRect.init(x: newReferenceViewRect.size.width - 2.0, y: newReferenceViewRect.origin.y, width: boundaryViewWidth, height: newReferenceViewRect.size.height - insets.bottom))
rightView.isUserInteractionEnabled = false
rightView.tag = 222
///////////
////Top////
///////////
let topView = UIView(frame: CGRect.init(x: newReferenceViewRect.origin.x, y: newReferenceViewRect.origin.y - (boundaryViewHeight - 1), width: newReferenceViewRect.size.width - insets.right, height: boundaryViewHeight))
topView.isUserInteractionEnabled = false
topView.tag = 322
//////////////
////Bottom////
//////////////
let bottomView = UIView(frame: CGRect.init(x: newReferenceViewRect.origin.x, y: newReferenceViewRect.size.height - 2.0, width: newReferenceViewRect.size.width - insets.right, height: boundaryViewHeight))
bottomView.isUserInteractionEnabled = false
bottomView.tag = 422
///////////////////
////Add Subview////
///////////////////
referenceView.addSubview(leftView)
referenceView.addSubview(rightView)
referenceView.addSubview(topView)
referenceView.addSubview(bottomView)
return [leftView, rightView, topView, bottomView]
This is how it works:
swift ios protocols uikit
I recently published an iOS control/component called BJDraggable
which basically, with a call of a method, enables us to drag a view within its superview boundary. The whole setup works using the UIKitDynamics
API. (Scroll to last to see the output achieved.)
These are the methods I expose to consumers. You could follow up from these method calls in the detailed code (BJDraggable.swift).
@objc protocol BJDraggable: class
@objc func addDraggability(withinView referenceView: UIView)
@objc func addDraggability(withinView referenceView: UIView, withMargin insets:UIEdgeInsets)
@objc func removeDraggability()
Here is my full code. Please bear with the length of the code; it is pretty long. Demo project is available here at GitHub. Thanks in advance for your time.
BJDraggable.swift
import UIKit
var kReferenceViewKey: String = "ReferenceViewKey"
var kDynamicAnimatorKey: String = "DynamicAnimatorKey"
var kAttachmentBehaviourKey: String = "AttachmentBehaviourKey"
var kPanGestureKey: String = "PanGestureKey"
var kResetPositionKey: String = "ResetPositionKey"
fileprivate enum BehaviourNames
case main
case border
case collision
case attachment
/**A simple protocol *(No need to implement methods and properties yourself. Just drop-in the BJDraggable file to your project and all done)* utilizing the powerful `UIKitDynamics` API, which makes **ANY** `UIView` draggable within a boundary view that acts as collision body, with a single method call.
*/
@objc protocol BJDraggable: class
/**
Gives you the power to drag your `UIView` anywhere within a specified view, and collide within its bounds.
- parameter referenceView: The boundary view which acts as a wall, and your view will collide with it and would never fall out of bounds hopefully. **Note that the reference view should contain the view that you're trying to add draggability to in its view hierarchy. The app would crash otherwise.**
*/
@objc func addDraggability(withinView referenceView: UIView)
/**
This single method call will give you the power to drag your `UIView` anywhere within a specified view, and collide within its bounds.
- parameter referenceView: This is the boundary view which acts as a wall, and your view will collide with it and would never fall out of bounds hopefully. **Note that the reference view should contain the view that you're trying to add draggability to in its view hierarchy. The app would crash otherwise.**
- parameter insets: If you want to make the boundary to be offset positively or negatively, you can specify that here. This is nothing but a margin for the boundary.
*/
@objc func addDraggability(withinView referenceView: UIView, withMargin insets:UIEdgeInsets)
/**
Removes the power from you, to drag the view in question
*/
@objc func removeDraggability()
///Implementation of `BJDraggable` protocol
extension UIView: BJDraggable
//
//////////////////////////////////////////////////////////////////////////////////////////
//MARK:-
//MARK: Properties
//MARK:-
//////////////////////////////////////////////////////////////////////////////////////////
//
public var shouldResetViewPositionAfterRemovingDraggability: Bool
get
let getValue = (objc_getAssociatedObject(self, &kResetPositionKey) as? Bool)
return getValue == nil ? false : getValue!
set
objc_setAssociatedObject(self, &kResetPositionKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
self.translatesAutoresizingMaskIntoConstraints = !newValue
fileprivate var referenceView: UIView?
get
return objc_getAssociatedObject(self, &kReferenceViewKey) as? UIView
set
objc_setAssociatedObject(self, &kReferenceViewKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
fileprivate var animator: UIDynamicAnimator?
get
return objc_getAssociatedObject(self, &kDynamicAnimatorKey) as? UIDynamicAnimator
set
objc_setAssociatedObject(self, &kDynamicAnimatorKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
fileprivate var attachmentBehaviour: UIAttachmentBehavior?
get
return objc_getAssociatedObject(self, &kAttachmentBehaviourKey) as? UIAttachmentBehavior
set
objc_setAssociatedObject(self, &kAttachmentBehaviourKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
fileprivate var panGestureRecognizer: UIPanGestureRecognizer?
get
return objc_getAssociatedObject(self, &kPanGestureKey) as? UIPanGestureRecognizer
set
objc_setAssociatedObject(self, &kPanGestureKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
//
//////////////////////////////////////////////////////////////////////////////////////////
//MARK:-
//MARK: Method Implementations
//MARK:-
//////////////////////////////////////////////////////////////////////////////////////////
//
final func addDraggability(withinView referenceView: UIView)
self.addDraggability(withinView: referenceView, withMargin: UIEdgeInsets.init(top: 0, left: 0, bottom: 0, right: 0))
final func addDraggability(withinView referenceView: UIView, withMargin insets:UIEdgeInsets)
guard self.animator == nil else return
///////////////////////
/////Configuration/////
///////////////////////
performInitialConfiguration()
addPanGestureRecognizer()
////////////////////////////////////////////////
/////Getting Collision Items For Behaviours/////
////////////////////////////////////////////////
let collisionItems = self.drawAndGetCollisionViewsAround(referenceView, withInsets: insets)
////////////////////
/////Behaviours/////
////////////////////
let mainItemBehaviour = get(behaviour: .main, for: referenceView, withInsets: insets, configuredWith: collisionItems)!
let borderItemsBehaviour = get(behaviour: .border, for: referenceView, withInsets: insets, configuredWith: collisionItems)!
let collisionBehaviour = get(behaviour: .collision, for: referenceView, withInsets: insets, configuredWith: collisionItems)!
let attachmentBehaviour = get(behaviour: .attachment, for: referenceView, withInsets: insets, configuredWith: collisionItems)!
//////////////////
/////Animator/////
//////////////////
let animator = UIDynamicAnimator.init(referenceView: referenceView)
animator.addBehavior(mainItemBehaviour)
animator.addBehavior(borderItemsBehaviour)
animator.addBehavior(collisionBehaviour)
animator.addBehavior(attachmentBehaviour)
/////////////////////
/////Persistence/////
/////////////////////
self.animator = animator
self.referenceView = referenceView
self.attachmentBehaviour = attachmentBehaviour as? UIAttachmentBehavior
final func removeDraggability()
if let recognizer = self.panGestureRecognizer self.removeGestureRecognizer(recognizer)
self.translatesAutoresizingMaskIntoConstraints = !self.shouldResetViewPositionAfterRemovingDraggability
self.animator?.removeAllBehaviors()
if let subviews = self.referenceView?.subviews
for view in subviews
self.referenceView = nil
self.attachmentBehaviour = nil
self.animator = nil
self.panGestureRecognizer = nil
//
//////////////////////////////////////////////////////////////////////////////////////////
//MARK:-
//MARK: Helpers 1
//MARK:-
//////////////////////////////////////////////////////////////////////////////////////////
//
fileprivate func performInitialConfiguration()
self.isUserInteractionEnabled = true
fileprivate func addPanGestureRecognizer()
let panGestureRecognizer = UIPanGestureRecognizer.init(target: self, action: #selector(self.panGestureHandler(_:)))
self.addGestureRecognizer(panGestureRecognizer)
self.panGestureRecognizer = panGestureRecognizer
@objc final func panGestureHandler(_ gesture: UIPanGestureRecognizer)
guard let referenceView = self.referenceView else return
let touchPoint = gesture.location(in: referenceView)
self.attachmentBehaviour?.anchorPoint = touchPoint
fileprivate func get(behaviour:BehaviourNames, for referenceView:UIView, withInsets:UIEdgeInsets, configuredWith boundaryCollisionItems:[UIDynamicItem]) -> UIDynamicBehavior?
let allItems = [self] + boundaryCollisionItems
switch behaviour
case .border:
let borderItemsBehaviour = UIDynamicItemBehavior.init(items: boundaryCollisionItems)
borderItemsBehaviour.allowsRotation = false
borderItemsBehaviour.isAnchored = true
borderItemsBehaviour.friction = 2.0
return borderItemsBehaviour
case .main:
let mainItemBehaviour = UIDynamicItemBehavior.init(items: [self])
mainItemBehaviour.allowsRotation = false
mainItemBehaviour.isAnchored = false
mainItemBehaviour.friction = 2.0
return mainItemBehaviour
case .collision:
let collisionBehaviour = UICollisionBehavior.init(items: allItems)
collisionBehaviour.collisionMode = .items
collisionBehaviour.addBoundary(withIdentifier: "Boundary" as NSCopying, for: self.boundaryPathFor(referenceView))
return collisionBehaviour
case .attachment:
let attachmentBehaviour = UIAttachmentBehavior.init(item: self, attachedToAnchor: self.center)
return attachmentBehaviour
//
//////////////////////////////////////////////////////////////////////////////////////////
//MARK:-
//MARK: Helpers 2
//MARK:-
//////////////////////////////////////////////////////////////////////////////////////////
//
func alteredFrameByPoints(_ point:CGFloat) -> CGRect
var newFrame = self.frame
newFrame.origin.x -= point
newFrame.origin.y -= point
newFrame.size.width += point * 2
newFrame.size.height += point * 2
return newFrame
fileprivate func boundaryPathFor(_ view:UIView) -> UIBezierPath
let cgPath = CGPath.init(rect: view.alteredFrameByPoints(2.0), transform:nil)
return UIBezierPath.init(cgPath: cgPath)
fileprivate func getNewRectFrom(rect:CGRect, byApplying insets:UIEdgeInsets) -> CGRect
var newRect:CGRect = .zero
let x = rect.origin.x + insets.left
let y = rect.origin.y + insets.top
let width = rect.width - insets.right
let height = rect.height - insets.bottom
newRect.origin.x = x
newRect.origin.y = y
newRect.size.width = width
newRect.size.height = height
return newRect
@discardableResult
fileprivate func drawAndGetCollisionViewsAround(_ referenceView:UIView, withInsets insets:UIEdgeInsets) -> ([UIView])
let boundaryViewWidth = CGFloat(1)
let boundaryViewHeight = CGFloat(1)
////////////////////
////Get New Rect////
////////////////////
let newReferenceViewRect = self.getNewRectFrom(rect:referenceView.alteredFrameByPoints(1),
byApplying:insets)
////////////
////Left////
////////////
let leftView = UIView(frame: CGRect.init(x: newReferenceViewRect.origin.x - (boundaryViewWidth - 1), y: newReferenceViewRect.origin.y, width: boundaryViewWidth, height: newReferenceViewRect.size.height - insets.bottom))
leftView.isUserInteractionEnabled = false
leftView.tag = 122
/////////////
////Right////
/////////////
let rightView = UIView(frame: CGRect.init(x: newReferenceViewRect.size.width - 2.0, y: newReferenceViewRect.origin.y, width: boundaryViewWidth, height: newReferenceViewRect.size.height - insets.bottom))
rightView.isUserInteractionEnabled = false
rightView.tag = 222
///////////
////Top////
///////////
let topView = UIView(frame: CGRect.init(x: newReferenceViewRect.origin.x, y: newReferenceViewRect.origin.y - (boundaryViewHeight - 1), width: newReferenceViewRect.size.width - insets.right, height: boundaryViewHeight))
topView.isUserInteractionEnabled = false
topView.tag = 322
//////////////
////Bottom////
//////////////
let bottomView = UIView(frame: CGRect.init(x: newReferenceViewRect.origin.x, y: newReferenceViewRect.size.height - 2.0, width: newReferenceViewRect.size.width - insets.right, height: boundaryViewHeight))
bottomView.isUserInteractionEnabled = false
bottomView.tag = 422
///////////////////
////Add Subview////
///////////////////
referenceView.addSubview(leftView)
referenceView.addSubview(rightView)
referenceView.addSubview(topView)
referenceView.addSubview(bottomView)
return [leftView, rightView, topView, bottomView]
This is how it works:
swift ios protocols uikit
edited Jul 6 at 6:47
asked Jun 24 at 16:30
Badhan Ganesh
1185
1185
add a comment |Â
add a comment |Â
1 Answer
1
active
oldest
votes
up vote
2
down vote
accepted
Comparing an optional value with nil
as in
return getValue == nil ? false : getValue!
is better done with the nil-coalescing operator ??
:
return getValue ?? false
It is shorter, avoids the forced-unwrapping, accesses the variable only once, and clearly expresses the intent.
(See also When should I compare an optional value to nil? on Stack Overflow.)
And now the intermediate variable is not needed anymore:
return objc_getAssociatedObject(self, &kResetPositionKey) as? Bool ?? false
The keys for the associated objects
var kReferenceViewKey: String = "ReferenceViewKey"
// ...
are global variables. To restrict their visibility, they can be made
âÂÂfile privateâÂÂ
fileprivate var kReferenceViewKey = "ReferenceViewKey"
// ...
or static properties, private to the extension:
extension UIView: BJDraggable
private static var kReferenceViewKey = "ReferenceViewKey"
// ...
Note also that the explicit type annotation is not necessary.
Only the address of the variable is needed as key for the associated
value, the type and value does not matter. You can even define it as
a single byte
private static var kReferenceViewKey: UInt8 = 0
to save some memory.
Here
if view.tag == 122 || view.tag == 222 || view.tag == 322 || view.tag == 422
âÂÂmagic tag numbersâÂÂàare used to identify the special views which were added earlier. That is error-prone, since the original UIView
might use
the same tags by chance.
An alternative would be to create a custom UIView
subclass for those
special views, or keep references to them in another (associated)
property.
This
func alteredFrameByPoints(_ point:CGFloat) -> CGRect
var newFrame = self.frame
newFrame.origin.x -= point
newFrame.origin.y -= point
newFrame.size.width += point * 2
newFrame.size.height += point * 2
return newFrame
can be simplified to
func alteredFrameByPoints(_ point:CGFloat) -> CGRect
return self.frame.insetBy(dx: -point, dy: -point)
and this function
fileprivate func getNewRectFrom(rect:CGRect, byApplying insets:UIEdgeInsets) -> CGRect
var newRect:CGRect = .zero
let x = rect.origin.x + insets.left
let y = rect.origin.y + insets.top
let width = rect.width - insets.right
let height = rect.height - insets.bottom
newRect.origin.x = x
newRect.origin.y = y
newRect.size.width = width
newRect.size.height = height
return newRect
is exactly what
UIEdgeInsetsInsetRect(rect, insets) // Swift <= 4.1
rect.inset(by: insets) // Swift >= 4.2
already does.
Thanks Martin, I will take your comments into account. I actually foundUIEdgeInsetsInsetRect
method right after I wrotegetNewRectFrom
. I didn't knew/search for an already existing one. Thanks again! :)
â Badhan Ganesh
Jun 24 at 19:11
The@objc
annotation exposes my methods to Objective-C code. Without those, I could not call them and I getNo visible @interface for 'Class' declares the selector 'myMethod:'
And yeah, global keys can be avoided, thanks.
â Badhan Ganesh
Jun 25 at 6:55
@BadhanGanesh: You are right, I hadn't thought about using the extension from Objective-C. I have removed that part from the answer.
â Martin R
Jun 25 at 7:14
add a comment |Â
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
2
down vote
accepted
Comparing an optional value with nil
as in
return getValue == nil ? false : getValue!
is better done with the nil-coalescing operator ??
:
return getValue ?? false
It is shorter, avoids the forced-unwrapping, accesses the variable only once, and clearly expresses the intent.
(See also When should I compare an optional value to nil? on Stack Overflow.)
And now the intermediate variable is not needed anymore:
return objc_getAssociatedObject(self, &kResetPositionKey) as? Bool ?? false
The keys for the associated objects
var kReferenceViewKey: String = "ReferenceViewKey"
// ...
are global variables. To restrict their visibility, they can be made
âÂÂfile privateâÂÂ
fileprivate var kReferenceViewKey = "ReferenceViewKey"
// ...
or static properties, private to the extension:
extension UIView: BJDraggable
private static var kReferenceViewKey = "ReferenceViewKey"
// ...
Note also that the explicit type annotation is not necessary.
Only the address of the variable is needed as key for the associated
value, the type and value does not matter. You can even define it as
a single byte
private static var kReferenceViewKey: UInt8 = 0
to save some memory.
Here
if view.tag == 122 || view.tag == 222 || view.tag == 322 || view.tag == 422
âÂÂmagic tag numbersâÂÂàare used to identify the special views which were added earlier. That is error-prone, since the original UIView
might use
the same tags by chance.
An alternative would be to create a custom UIView
subclass for those
special views, or keep references to them in another (associated)
property.
This
func alteredFrameByPoints(_ point:CGFloat) -> CGRect
var newFrame = self.frame
newFrame.origin.x -= point
newFrame.origin.y -= point
newFrame.size.width += point * 2
newFrame.size.height += point * 2
return newFrame
can be simplified to
func alteredFrameByPoints(_ point:CGFloat) -> CGRect
return self.frame.insetBy(dx: -point, dy: -point)
and this function
fileprivate func getNewRectFrom(rect:CGRect, byApplying insets:UIEdgeInsets) -> CGRect
var newRect:CGRect = .zero
let x = rect.origin.x + insets.left
let y = rect.origin.y + insets.top
let width = rect.width - insets.right
let height = rect.height - insets.bottom
newRect.origin.x = x
newRect.origin.y = y
newRect.size.width = width
newRect.size.height = height
return newRect
is exactly what
UIEdgeInsetsInsetRect(rect, insets) // Swift <= 4.1
rect.inset(by: insets) // Swift >= 4.2
already does.
Thanks Martin, I will take your comments into account. I actually foundUIEdgeInsetsInsetRect
method right after I wrotegetNewRectFrom
. I didn't knew/search for an already existing one. Thanks again! :)
â Badhan Ganesh
Jun 24 at 19:11
The@objc
annotation exposes my methods to Objective-C code. Without those, I could not call them and I getNo visible @interface for 'Class' declares the selector 'myMethod:'
And yeah, global keys can be avoided, thanks.
â Badhan Ganesh
Jun 25 at 6:55
@BadhanGanesh: You are right, I hadn't thought about using the extension from Objective-C. I have removed that part from the answer.
â Martin R
Jun 25 at 7:14
add a comment |Â
up vote
2
down vote
accepted
Comparing an optional value with nil
as in
return getValue == nil ? false : getValue!
is better done with the nil-coalescing operator ??
:
return getValue ?? false
It is shorter, avoids the forced-unwrapping, accesses the variable only once, and clearly expresses the intent.
(See also When should I compare an optional value to nil? on Stack Overflow.)
And now the intermediate variable is not needed anymore:
return objc_getAssociatedObject(self, &kResetPositionKey) as? Bool ?? false
The keys for the associated objects
var kReferenceViewKey: String = "ReferenceViewKey"
// ...
are global variables. To restrict their visibility, they can be made
âÂÂfile privateâÂÂ
fileprivate var kReferenceViewKey = "ReferenceViewKey"
// ...
or static properties, private to the extension:
extension UIView: BJDraggable
private static var kReferenceViewKey = "ReferenceViewKey"
// ...
Note also that the explicit type annotation is not necessary.
Only the address of the variable is needed as key for the associated
value, the type and value does not matter. You can even define it as
a single byte
private static var kReferenceViewKey: UInt8 = 0
to save some memory.
Here
if view.tag == 122 || view.tag == 222 || view.tag == 322 || view.tag == 422
âÂÂmagic tag numbersâÂÂàare used to identify the special views which were added earlier. That is error-prone, since the original UIView
might use
the same tags by chance.
An alternative would be to create a custom UIView
subclass for those
special views, or keep references to them in another (associated)
property.
This
func alteredFrameByPoints(_ point:CGFloat) -> CGRect
var newFrame = self.frame
newFrame.origin.x -= point
newFrame.origin.y -= point
newFrame.size.width += point * 2
newFrame.size.height += point * 2
return newFrame
can be simplified to
func alteredFrameByPoints(_ point:CGFloat) -> CGRect
return self.frame.insetBy(dx: -point, dy: -point)
and this function
fileprivate func getNewRectFrom(rect:CGRect, byApplying insets:UIEdgeInsets) -> CGRect
var newRect:CGRect = .zero
let x = rect.origin.x + insets.left
let y = rect.origin.y + insets.top
let width = rect.width - insets.right
let height = rect.height - insets.bottom
newRect.origin.x = x
newRect.origin.y = y
newRect.size.width = width
newRect.size.height = height
return newRect
is exactly what
UIEdgeInsetsInsetRect(rect, insets) // Swift <= 4.1
rect.inset(by: insets) // Swift >= 4.2
already does.
Thanks Martin, I will take your comments into account. I actually foundUIEdgeInsetsInsetRect
method right after I wrotegetNewRectFrom
. I didn't knew/search for an already existing one. Thanks again! :)
â Badhan Ganesh
Jun 24 at 19:11
The@objc
annotation exposes my methods to Objective-C code. Without those, I could not call them and I getNo visible @interface for 'Class' declares the selector 'myMethod:'
And yeah, global keys can be avoided, thanks.
â Badhan Ganesh
Jun 25 at 6:55
@BadhanGanesh: You are right, I hadn't thought about using the extension from Objective-C. I have removed that part from the answer.
â Martin R
Jun 25 at 7:14
add a comment |Â
up vote
2
down vote
accepted
up vote
2
down vote
accepted
Comparing an optional value with nil
as in
return getValue == nil ? false : getValue!
is better done with the nil-coalescing operator ??
:
return getValue ?? false
It is shorter, avoids the forced-unwrapping, accesses the variable only once, and clearly expresses the intent.
(See also When should I compare an optional value to nil? on Stack Overflow.)
And now the intermediate variable is not needed anymore:
return objc_getAssociatedObject(self, &kResetPositionKey) as? Bool ?? false
The keys for the associated objects
var kReferenceViewKey: String = "ReferenceViewKey"
// ...
are global variables. To restrict their visibility, they can be made
âÂÂfile privateâÂÂ
fileprivate var kReferenceViewKey = "ReferenceViewKey"
// ...
or static properties, private to the extension:
extension UIView: BJDraggable
private static var kReferenceViewKey = "ReferenceViewKey"
// ...
Note also that the explicit type annotation is not necessary.
Only the address of the variable is needed as key for the associated
value, the type and value does not matter. You can even define it as
a single byte
private static var kReferenceViewKey: UInt8 = 0
to save some memory.
Here
if view.tag == 122 || view.tag == 222 || view.tag == 322 || view.tag == 422
âÂÂmagic tag numbersâÂÂàare used to identify the special views which were added earlier. That is error-prone, since the original UIView
might use
the same tags by chance.
An alternative would be to create a custom UIView
subclass for those
special views, or keep references to them in another (associated)
property.
This
func alteredFrameByPoints(_ point:CGFloat) -> CGRect
var newFrame = self.frame
newFrame.origin.x -= point
newFrame.origin.y -= point
newFrame.size.width += point * 2
newFrame.size.height += point * 2
return newFrame
can be simplified to
func alteredFrameByPoints(_ point:CGFloat) -> CGRect
return self.frame.insetBy(dx: -point, dy: -point)
and this function
fileprivate func getNewRectFrom(rect:CGRect, byApplying insets:UIEdgeInsets) -> CGRect
var newRect:CGRect = .zero
let x = rect.origin.x + insets.left
let y = rect.origin.y + insets.top
let width = rect.width - insets.right
let height = rect.height - insets.bottom
newRect.origin.x = x
newRect.origin.y = y
newRect.size.width = width
newRect.size.height = height
return newRect
is exactly what
UIEdgeInsetsInsetRect(rect, insets) // Swift <= 4.1
rect.inset(by: insets) // Swift >= 4.2
already does.
Comparing an optional value with nil
as in
return getValue == nil ? false : getValue!
is better done with the nil-coalescing operator ??
:
return getValue ?? false
It is shorter, avoids the forced-unwrapping, accesses the variable only once, and clearly expresses the intent.
(See also When should I compare an optional value to nil? on Stack Overflow.)
And now the intermediate variable is not needed anymore:
return objc_getAssociatedObject(self, &kResetPositionKey) as? Bool ?? false
The keys for the associated objects
var kReferenceViewKey: String = "ReferenceViewKey"
// ...
are global variables. To restrict their visibility, they can be made
âÂÂfile privateâÂÂ
fileprivate var kReferenceViewKey = "ReferenceViewKey"
// ...
or static properties, private to the extension:
extension UIView: BJDraggable
private static var kReferenceViewKey = "ReferenceViewKey"
// ...
Note also that the explicit type annotation is not necessary.
Only the address of the variable is needed as key for the associated
value, the type and value does not matter. You can even define it as
a single byte
private static var kReferenceViewKey: UInt8 = 0
to save some memory.
Here
if view.tag == 122 || view.tag == 222 || view.tag == 322 || view.tag == 422
âÂÂmagic tag numbersâÂÂàare used to identify the special views which were added earlier. That is error-prone, since the original UIView
might use
the same tags by chance.
An alternative would be to create a custom UIView
subclass for those
special views, or keep references to them in another (associated)
property.
This
func alteredFrameByPoints(_ point:CGFloat) -> CGRect
var newFrame = self.frame
newFrame.origin.x -= point
newFrame.origin.y -= point
newFrame.size.width += point * 2
newFrame.size.height += point * 2
return newFrame
can be simplified to
func alteredFrameByPoints(_ point:CGFloat) -> CGRect
return self.frame.insetBy(dx: -point, dy: -point)
and this function
fileprivate func getNewRectFrom(rect:CGRect, byApplying insets:UIEdgeInsets) -> CGRect
var newRect:CGRect = .zero
let x = rect.origin.x + insets.left
let y = rect.origin.y + insets.top
let width = rect.width - insets.right
let height = rect.height - insets.bottom
newRect.origin.x = x
newRect.origin.y = y
newRect.size.width = width
newRect.size.height = height
return newRect
is exactly what
UIEdgeInsetsInsetRect(rect, insets) // Swift <= 4.1
rect.inset(by: insets) // Swift >= 4.2
already does.
edited Jun 26 at 10:57
answered Jun 24 at 19:00
Martin R
14k12157
14k12157
Thanks Martin, I will take your comments into account. I actually foundUIEdgeInsetsInsetRect
method right after I wrotegetNewRectFrom
. I didn't knew/search for an already existing one. Thanks again! :)
â Badhan Ganesh
Jun 24 at 19:11
The@objc
annotation exposes my methods to Objective-C code. Without those, I could not call them and I getNo visible @interface for 'Class' declares the selector 'myMethod:'
And yeah, global keys can be avoided, thanks.
â Badhan Ganesh
Jun 25 at 6:55
@BadhanGanesh: You are right, I hadn't thought about using the extension from Objective-C. I have removed that part from the answer.
â Martin R
Jun 25 at 7:14
add a comment |Â
Thanks Martin, I will take your comments into account. I actually foundUIEdgeInsetsInsetRect
method right after I wrotegetNewRectFrom
. I didn't knew/search for an already existing one. Thanks again! :)
â Badhan Ganesh
Jun 24 at 19:11
The@objc
annotation exposes my methods to Objective-C code. Without those, I could not call them and I getNo visible @interface for 'Class' declares the selector 'myMethod:'
And yeah, global keys can be avoided, thanks.
â Badhan Ganesh
Jun 25 at 6:55
@BadhanGanesh: You are right, I hadn't thought about using the extension from Objective-C. I have removed that part from the answer.
â Martin R
Jun 25 at 7:14
Thanks Martin, I will take your comments into account. I actually found
UIEdgeInsetsInsetRect
method right after I wrote getNewRectFrom
. I didn't knew/search for an already existing one. Thanks again! :)â Badhan Ganesh
Jun 24 at 19:11
Thanks Martin, I will take your comments into account. I actually found
UIEdgeInsetsInsetRect
method right after I wrote getNewRectFrom
. I didn't knew/search for an already existing one. Thanks again! :)â Badhan Ganesh
Jun 24 at 19:11
The
@objc
annotation exposes my methods to Objective-C code. Without those, I could not call them and I get No visible @interface for 'Class' declares the selector 'myMethod:'
And yeah, global keys can be avoided, thanks.â Badhan Ganesh
Jun 25 at 6:55
The
@objc
annotation exposes my methods to Objective-C code. Without those, I could not call them and I get No visible @interface for 'Class' declares the selector 'myMethod:'
And yeah, global keys can be avoided, thanks.â Badhan Ganesh
Jun 25 at 6:55
@BadhanGanesh: You are right, I hadn't thought about using the extension from Objective-C. I have removed that part from the answer.
â Martin R
Jun 25 at 7:14
@BadhanGanesh: You are right, I hadn't thought about using the extension from Objective-C. I have removed that part from the answer.
â Martin R
Jun 25 at 7:14
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f197166%2fadding-dragging-ability-to-a-uiview-using-protocols%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password