a sexy and nasty gift of Objective-C to Swift
Hi, today I have something special to blog on. I heard so many times from one of my colleague; that in Ruby in Rails we have make a label draggable just by stating it as draggable. (i.e. the label can be dragged around anywhere in screen) Like:
class DraggableLabel: UILabel, Draggable {
}
No any method implementation required. Not method calls is required. Like
label.makeDraggable()
is not required.
I was thinking ,if I have to create draggable label than probably I would add code in override of awakeFromNib() or overriding some other functions.
Please, let me know how would you do that? Probably, the thing I am going to explain below might be primitive than yours. 🙂
I am using method swizzling in swift to create this functionality. If you know nothing about method swizzling, I have blogged about method swizzling in my previous blog. Check it out here!
Actually, let me do something else. I will make any UIView draggable then we can make UITextField or UILabel or any other subclass of UIView be draggable.
1. lets define Draggable protocol:
protocol Draggable {
}
I don’t have anything in mind to do other than dragging so I haven’t kept any function or variables in protocol.
2. Now main part is swizzling see the code below:
// 1: the function that swizzles the awakeFromNib method with new method (global function)
private func swizzling(_ view: UIView.Type) -> () {
// get selectors
let originalSelector = #selector(view.awakeFromNib)
let swizzledSelector = #selector(view.newAwakeFromNib)
// extract methods from selector
let originalMethod = class_getInstanceMethod(view, originalSelector)
let swizzledMethod = class_getInstanceMethod(view, swizzledSelector)
// exchange their implementation (swizzle)
method_exchangeImplementations(originalMethod, swizzledMethod)
}
extension UIView {
// 2 : swizzle methods in initialize
// only class func can be override in extension so initialize() is the best option
open override class func initialize() {
swizzling(self)
}
// 3 : New function that will be executed instead of awakeFromNib
// MARK: - Method Swizzling
@objc fileprivate func newAwakeFromNib() {
// I also want to call old awakeFromNib so I called this function: remember after swizzling the old function name becomes swizzled function’s name
self.newAwakeFromNib()
// 4 : if this view has implemented Draggable protocol, then enable do followings
if (self as? Draggable) != nil {
self.isUserInteractionEnabled = true
self.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(tappedOnView(gesture:))))
}
}
// 5 : Code to handle pan gesture on this view
@objc private func tappedOnView(gesture: UIPanGestureRecognizer) {
let newCenter = gesture.translation(in: self.superview)
self.transform = CGAffineTransform(translationX: newCenter.x, y: newCenter.y)
switch gesture.state {
case .ended:
let oldCenterX = self.center.x
let oldCenterY = self.center.y
self.center = CGPoint(x: oldCenterX + newCenter.x, y: oldCenterY + newCenter.y)
self.transform = CGAffineTransform.identity
default: break
}
}
}
Now I will create a UILabel which will be draggable:
class CustomLabel: UILabel, Draggable {
}
Simple Yeah?
Now I will create UITextView which is draggable:
class CustomLabel: UITextField, Draggable {
}
Cool Yeah?
To use these draggable UIView, you need to assign class from storyboard or create from code like you create other views.
Other Example on method swizzling: Example