Adding dragging ability to a UIView using protocols

The name of the pictureThe name of the pictureThe name of the pictureClash 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:



Output







share|improve this question



























    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:



    Output







    share|improve this question























      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:



      Output







      share|improve this question













      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:



      Output









      share|improve this question












      share|improve this question




      share|improve this question








      edited Jul 6 at 6:47
























      asked Jun 24 at 16:30









      Badhan Ganesh

      1185




      1185




















          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.






          share|improve this answer























          • 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











          • @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










          Your Answer




          StackExchange.ifUsing("editor", function ()
          return StackExchange.using("mathjaxEditing", function ()
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          );
          );
          , "mathjax-editing");

          StackExchange.ifUsing("editor", function ()
          StackExchange.using("externalEditor", function ()
          StackExchange.using("snippets", function ()
          StackExchange.snippets.init();
          );
          );
          , "code-snippets");

          StackExchange.ready(function()
          var channelOptions =
          tags: "".split(" "),
          id: "196"
          ;
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function()
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled)
          StackExchange.using("snippets", function()
          createEditor();
          );

          else
          createEditor();

          );

          function createEditor()
          StackExchange.prepareEditor(
          heartbeatType: 'answer',
          convertImagesToLinks: false,
          noModals: false,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          );



          );








           

          draft saved


          draft discarded


















          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






























          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.






          share|improve this answer























          • 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











          • @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














          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.






          share|improve this answer























          • 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











          • @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












          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.






          share|improve this answer















          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.







          share|improve this answer















          share|improve this answer



          share|improve this answer








          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 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











          • @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










          • 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















          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












           

          draft saved


          draft discarded


























           


          draft saved


          draft discarded














          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













































































          Popular posts from this blog

          Greedy Best First Search implementation in Rust

          Function to Return a JSON Like Objects Using VBA Collections and Arrays

          C++11 CLH Lock Implementation