One thing that I really miss when using Swift is Objective-C’s runtime. I don’t go crazy with it, but its power, when used wisely, is highly effective.
I had an idea a few weeks ago for a fun little app and thought this would be a great time to build an app from the ground up using Swift. I have a lot of convenience classes and logic written in Objective-C that needed to be ported over. One the first, and most important categories, is on UIView
. I extend the class to include the ability to add a tap gesture.
Over the years I’ve moved over to use Oliver Drobnik’s technique. It is cleaner than my original approach.
When Swift was announced, Apple assured developers that you could mix and match Objective-C and Swift. Most things you can. One of the skeptisms I originally had concerned the runtime. Mattt Thompson posted a great piece on NSHipster that gives some great examples on how to do it. I used that article as the main reference when starting to port my category over. When I went to compile the project I got the following error.
error: type ‘() -> Void’ does not conform to protocol ‘AnyObject’
var tapAction: (() -> Void)? {
get {
objc_getAssociatedObject(self, &AssociatedKeys.SNGLSActionHandlerTapBlockKey)
}
set {
objc_setAssociatedObject(
self,
&AssociatedKeys.SNGLSActionHandlerTapBlockKey,
newValue,
UInt(OBJC_ASSOCIATION_COPY_NONATOMIC)
)
}
}
I wrestled with this for quite sometime. I understood what it was saying, but didn’t have an elegant solution for it or hell an unelegant solution. Part of my problem was that I was thinking in terms of Objective-C and not Swift. After posting to StackOverflow Greg Heo pointed me in the right direction. How do make pass in a Void type into something that must conform to AnyObject
…you have to wrap it in a class.
Well that is simple enough:
class ClosureWrapper {
var closure: (() -> Void)?
init(closure: (() -> Void)?) {
self.closure = closure
}
}
var tapAction: (() -> Void)? {
get {
if let cl = objc_getAssociatedObject(self, "key") as ClosureWrapper {
return cl.closure
}
return nil
}
set {
objc_setAssociatedObject(
self,
"key",
ClosureWrapper(newValue),
UInt(OBJC_ASSOCIATION_COPY_NONATOMIC)
)
}
}
Not so fast. That is the right concept, but there is some additional work that you have to do. The ClosureWrapper
needs to inherit from NSObject
and conform NSCopying
.
Now I’m able to add a tap gesture to any UIView subclass.