Swizzle Tap Gesture Action in iOS Cocoa Touch Framework - ios

I am developing an Cocoa Touch Framework or a dynamic Library which would capture user actions using Swift.
E.g. When a user taps on a label, my framework should know about it.
For achieving this behaviour, I am using method swizzling.
I am creating an extension for the UITapGestureRecognizer.In this extension, in initialize method , I am swizzling the init :
open override class func initialize() {
guard self === UITapGestureRecognizer.self else { return }
let originalSelector = #selector(self.init(target:action:))
let swizzledSelector = #selector(swizzled_init(target:action:))
swizzling(self, originalSelector, swizzledSelector)
}
func swizzled_init(target: Any, action: Selector) {
swizzled_init(target : target,action: action)
print("swizzled_init"+action.description)
//This part is for swizzling the the action which is being provided by the developer at the time of init.
guard self === UITapGestureRecognizer.self else { return }
let originalSelector = action
let swizzledSelector = #selector(swizzled_action(sender:))
swizzling(self.classForCoder, originalSelector, swizzledSelector)
}
func swizzled_action(sender : UITapGestureRecognizer? = nil){
print("swizzled_action"+(sender?.description)!)
}
After swizzling init, the instance of tap is nil in my ViewController.
let tap = UITapGestureRecognizer.init(target: self, action: #selector(buttonTapped(sender:)))
And the second issue is this in the swizzled_init method:
guard self === UITapGestureRecognizer.self else { return }
which is getting failed.
Could you please direct me in resolving these issues?

Related

Set global default back button display mode for all UIViewController instances

iOS 14 introduced configurable Back button mode.
Eg. you can have "Back" label text on the button, but in the compact history menu you can still see the proper title of previous controllers.
I'm looking for a easy, pleasant way to configure the default mode, so all UIViewController instances during runtime will have default mode set, like UINavigationItemBackButtonDisplayModeGeneric
I wonder if there is a way to do that without subclassing UIViewController or remember to always configure manually every instance of UIViewController
(via viewController.navigationItem.backButtonDisplayMode = UINavigationItemBackButtonDisplayModeGeneric).
Any handy method which does not require extensive refactoring of hundreds of UIViewController instances greatly appreciated!
Without subclassing I think is not possible since navigationItem requires an instance to work with and can't be modified directly from extensions
class GenericViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// your code here
viewController.navigationItem.backButtonDisplayMode = UINavigationItemBackButtonDisplayModeGeneric
}
}
And use that where ever you need
class viewController: GenericViewController
This is a really good approach since you have control over what implements it and what's not considering that it might not be there in all scenes
To solve the same problem, I used the swizzling technique
import UIKit
private let swizzling: (UIViewController.Type, Selector, Selector) -> Void = { forClass, originalSelector, swizzledSelector in
if let originalMethod = class_getInstanceMethod(forClass, originalSelector), let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector) {
let didAddMethod = class_addMethod(forClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
if didAddMethod {
class_replaceMethod(forClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
}
extension UIViewController {
static func swizzle() {
let originalSelector1 = #selector(viewDidLoad)
let swizzledSelector1 = #selector(swizzled_viewDidLoad)
swizzling(UIViewController.self, originalSelector1, swizzledSelector1)
}
#objc open func swizzled_viewDidLoad() {
if let _ = navigationController {
if #available(iOS 14.0, *) {
navigationItem.backButtonDisplayMode = .generic
} else {
// Fallback on earlier versions
navigationItem.backButtonTitle = "Back"
}
}
swizzled_viewDidLoad()
}
}
And in application(_:didFinishLaunchingWithOptions:) call
UIViewController.swizzle()

Repeat same statement in all viewDidLoad functions possible extension file

I want this same statement repeated in all of my projects' viewDidLoad functions. I know I can just manually type it in but I am trying to find a way to increase my code speed. I don't know if I can use a extension file in this.
override func viewDidLoad() {
super.viewDidLoad()
let myswitchBoolValuefromFirstVc : Bool = UserDefaults.standard.bool(forKey: "mySwitch")// this is how you retrieve the bool value
// to see the value, just print those with conditions. you can use those for your things.
if myswitchBoolValuefromFirstVc == true {
print("true")
rosaryCounterLabel.isHidden = false
}
else {
print("false")
rosaryCounterLabel.isHidden = true
}
If you are prepared to abuse the Objective-C runtime that UIViewController still uses, you can use method swizzling to do what you ask. https://medium.com/#abhimuralidharan/method-swizzling-in-ios-swift-1f38edaf984f
let aClass: AnyClass! = object_getClass(instance)
let originalMethod = class_getInstanceMethod(aClass, #selector(viewDidLoad))
let swizzledMethod = class_getInstanceMethod(aClass, #selector(newViewDidLoad))
if let originalMethod = originalMethod, let swizzledMethod = swizzledMethod {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
but I wouldn't recommend it. You trade between discoverability and repetition. In the case where you control all the code, it is going to be easier to maintain using a solution like subclassing that will still require some changes in every view controller.
Create a "master" view controller.
class MasterViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//"Global" code here
}
}
And then inherit in all your other view controllers
class ViewController: MasterViewController{
override func viewDidLoad() {
super.viewDidLoad()
//controller specific code here
}
}

Swift - method swizzling

This is the method swizzling code written in Objective-C. I am having a hard time converting this in Swift.
void MPApplicationDidRegisterForRemoteNotificationsWithDeviceToken(id self, SEL _cmd, UIApplication *application, NSData *deviceToken) {
[[MPPush shared] appRegisteredForRemoteNotificationsWithDeviceToken:deviceToken];
IMP original = [MPAppDelegateProxy originalImplementation:_cmd class:[self class]];
if (original)
((void(*)(id, SEL, UIApplication *, NSData*))original)(self, _cmd, application, deviceToken);
}
Swiftify isn't converting the above code correctly.
I tried to do this but I am not sure how to pass parameters and use the exact above parameters in Swift swizzling method. This is my fail attempt to convert the above in Swift (code doesn't even compile) :
var MPApplicationDidRegisterForRemoteNotificationsWithDeviceToken: Void {
// TODO: MPPush.shared.app
let original = MPAppDelegateProxy.proxyAppDelegate.originalImplementation(selector: cmd, forClass: type(of: self))
}(self: Any, _cmd: Selector, application: UIApplication, deviceToken: Data)
Extend your class:
extension YourClassName {
static let classInit: () -> () = {
let originalSelector = #selector(originalFunction)
let swizzledSelector = #selector(swizzledFunction)
swizzle(YourClassName.self, originalSelector, swizzledSelector)
}
#objc func swizzledFunction() {
//Your new implementation
}
}
Your class (YourClassName) should inherit from NSObject and originalSelector should be a dynamic method.
swizzle is a closure that exchanges the implementations:
private let swizzle: (AnyClass, Selector, Selector) -> () = { fromClass, originalSelector, swizzledSelector in
guard
let originalMethod = class_getInstanceMethod(fromClass, originalSelector),
let swizzledMethod = class_getInstanceMethod(fromClass, swizzledSelector)
else { return }
method_exchangeImplementations(originalMethod, swizzledMethod)
}
You could define swizzle in the class where you are going to do the swizzling. For example the AppDelegate class definition. And then do the swizzling in AppDelegate.init():
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
override init() {
super.init()
YourClassName.classInit()
}
}
Example
Here is a concrete example of swizzling, it exchanges the implementation of two methods that take one argument:
class Example: NSObject {
#objc dynamic func sayHi(to name: String) {
print("Hi", name)
}
}
extension Example {
public class func swizzleMethod() {
guard
let originalMethod = class_getInstanceMethod(Example.self, #selector(Example.sayHi(to:))),
let swizzledMethod = class_getInstanceMethod(Example.self, #selector(Example.sayHello(to:)))
else { return }
method_exchangeImplementations(originalMethod, swizzledMethod)
}
#objc func sayHello(to name: String) {
print("Hello", name)
}
}
Example.swizzleMethod()
let a = Example()
a.sayHi(to: "Nitish") //Hello Nitish
Swift Native swizzling
As of Swift 5.1, there is a native version of method swizzling that does not rely on Objective-C’s message passing. The #_dynamicReplacement modifier can be used upon a replacement function, and takes as an argument the name of the function that it should replace. The function that is being replaced must be marked with the #dynamic modifier, unless -enable-implicit-dynamic compilation flag is used, which makes the compiler assume that every eligible entity has been marked with the modifier.
For example:
dynamic func original() {
print("I am the original")
}
#_dynamicReplacement(for: original)
func replacement() {
print("I am the replacement")
}
original() // prints "I am the replacement"
For more details on dynamic method replacement, visit this Swift forum page.

swift functions with default parameters also a selector?

I wanted to be able to call this function from two places: When I finish editing a text field, I want to add a new webView when there are none in a stackView, and I also want to be able to use a barButtonItem to do so.
I'm having two problems. when the bar button calls this function, the parameter 'url', becomes an object, type UIBarButtonItem. when it's called from textFieldShouldReturn, it properly comes in as an NSURL. if the user doesn't type anything in the address field, and hits enter, a blank NSURL comes in, and the default value is not used. (i'd like it to be)
what should the call look like from the textfieldShouldReturn function, so that a blank will trigger the default?
how do i handle the fact that either my function or the button will call the function, and why does my named parameter 'url' become what i guess would be 'sender?'
override func viewDidLoad() {
super.viewDidLoad()
setDefaultTitle()
let add = UIBarButtonItem(barButtonSystemItem: .Add, target: self, action: #selector(ViewController.addWebView))
let delete = UIBarButtonItem(barButtonSystemItem: .Trash, target: self, action: #selector(ViewController.deleteWebView))
navigationItem.rightBarButtonItems = [delete, add]
}
func addWebView(url: NSURL = NSURL(string: "https://www.google.com")!) {
let webView = UIWebView()
webView.delegate = self
stackView .addArrangedSubview(webView)
webView.loadRequest(NSURLRequest(URL: url))
webView.layer.borderColor = UIColor.blueColor().CGColor
selectWebView(webView)
let recognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.webViewTapped))
recognizer.delegate = self
webView.addGestureRecognizer(recognizer)
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
if let webView = activeWebView, address = addressBar.text {
if let url = NSURL(string: address) {
webView.loadRequest(NSURLRequest(URL: url))
}
} else if stackView.arrangedSubviews.count == 0 {
let address = NSURL(string: addressBar.text!)!
addWebView(address)
}
textField.resignFirstResponder()
return true
}
That's right that you are getting sender object which is actually UIBarButtonItem. Have you heard about Target-Action Cocoa pattern? If no, you can read more here:
https://developer.apple.com/library/ios/documentation/General/Conceptual/Devpedia-CocoaApp/TargetAction.html
Especially relevant section to you is "An Action Method Must Have a Certain Form".
Consider to introduce addWebView overload:
func addWebView(sender: NSObject) {
addWebView(url: NSURL(string: "https://www.google.com")!)
}
private func addWebView(url: NSURL) {
//left as is ...
}
Here is update per Dave's comments.
Have to use different name for actual implementation method. Otherwise Swift compiler is failed to resolve the assigned selector name.
Useful code, which demonstrates the problem is attached below:
class Notifier: NSObject {
private var _target: NSObject!
private var _action: Selector!
func addObserver(target: NSObject, action: Selector) {
_target = target
_action = action
}
func invokeMethod() {
guard let t = _target else {
print("target must be set")
return
}
guard let a = _action else {
print("action must be set")
return
}
if t.respondsToSelector(a) {
t.performSelector(a, withObject: self)
}
}
}
class Observer: NSObject {
func subscribe(notifier: Notifier) {
notifier.addObserver(self, action: #selector(Observer.callback))
}
func callback(sender: NSObject) {
callbackImpl(NSURL(string: "https://www.google.com")!)
}
private func callbackImpl(url: NSURL) {
print("url\(url)")
}
}
//client's code
let n = Notifier()
let o = Observer()
o.subscribe(n)
n.invokeMethod()

Method Swizzling does not work

I would like to make use of method swizzling, but am unable to get even simple examples to work for me. It is possible that I am misunderstanding what the concept is, but as far as I know it allows for method implementations to be swapped.
Given two methods, A and B, I would like to swap their implementations such that calling A would execute B instead. I came across a few examples of swizzling (example1 and example2). I created a new project with a class to test this.
class Swizzle: NSObject
{
func method()
{
print("A");
}
}
extension Swizzle
{
override class func initialize()
{
struct Static
{
static var token: dispatch_once_t = 0;
}
// make sure this isn't a subclass
if (self !== Swizzle.self)
{
return;
}
dispatch_once(&Static.token)
{
let originalSelector = Selector("method");
let swizzledSelector = Selector("methodExt");
let originalMethod = class_getInstanceMethod(self, originalSelector);
let swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
print(method_getImplementation(originalMethod));
print(method_getImplementation(swizzledMethod));
let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if didAddMethod
{
class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}
else
{
method_exchangeImplementations(originalMethod, swizzledMethod);
}
print(method_getImplementation(originalMethod));
print(method_getImplementation(swizzledMethod));
}
}
func methodExt()
{
print("B");
}
}
I then try to execute it with
var s = Swizzle();
s.method();
The expected output is "B", but "A" is still being printed. As you can see from my code, I've included prints of each IMP before and after the swizzle operation. These prints show that the exchange does take place, yet the output remains the same.
Output:
0x000000010251a920
0x000000010251ad40
0x000000010251ad40
0x000000010251a920
A
Is there anything I am missing when it comes to getting these changes to take effect?
PS. Currently using XCode 7.0.1
The issue is that your method() lacks the dynamic directive:
class Swizzle: NSObject
{
dynamic func method()
{
print("A")
}
}
Modify the declaration and it should work.
When using method swizzling in Swift there are two requirements that your classes/methods must comply with:
Your class must extend NSObject
The functions you want to swizzle must have the dynamic attribute
For a complete explanation of why this is required, check out Using Swift with Cocoa and Objective-C:
Requiring Dynamic Dispatch
While the #objc attribute exposes your Swift API to the Objective-C
runtime, it does not guarantee dynamic dispatch of a property, method,
subscript, or initializer. The Swift compiler may still devirtualize
or inline member access to optimize the performance of your code,
bypassing the Objective-C runtime. When you mark a member declaration
with the dynamic modifier, access to that member is always dynamically
dispatched. Because declarations marked with the dynamic modifier are
dispatched using the Objective-C runtime, they’re implicitly marked
with the #objc attribute.
Requiring dynamic dispatch is rarely necessary. However, you must use
the dynamic modifier when you know that the implementation of an API
is replaced at runtime. For example, you can use the
method_exchangeImplementations function in the Objective-C runtime to
swap out the implementation of a method while an app is running. If
the Swift compiler inlined the implementation of the method or
devirtualized access to it, the new implementation would not be used.
Swift 3 Update:
There have been a few changes in regard to GCD and dispatch_once is not available anymore. To perform the same one time operation, we can enclose the code in the initialization block of a global static class constant.
The Swift language guarantees that this code will be executed only once during the lifetime of the application.
class TestSwizzling : NSObject {
dynamic func methodOne()->Int{
return 1
}
}
extension TestSwizzling {
//In Objective-C you'd perform the swizzling in load(),
//but this method is not permitted in Swift
override class func initialize()
{
struct Inner {
static let i: () = {
let originalSelector = #selector(TestSwizzling.methodOne)
let swizzledSelector = #selector(TestSwizzling.methodTwo)
let originalMethod = class_getInstanceMethod(TestSwizzling.self, originalSelector);
let swizzledMethod = class_getInstanceMethod(TestSwizzling.self, swizzledSelector)
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
let _ = Inner.i
}
func methodTwo()->Int{
// It will not be a recursive call anymore after the swizzling
return methodTwo()+1
}
}
var c = TestSwizzling()
print(c.methodOne())
print(c.methodTwo())
Swift 2.2 Update:
I've updated the original example for the new #selector attribute:
class TestSwizzling : NSObject {
dynamic func methodOne()->Int{
return 1
}
}
extension TestSwizzling {
//In Objective-C you'd perform the swizzling in load(),
//but this method is not permitted in Swift
override class func initialize()
{
struct Static
{
static var token: dispatch_once_t = 0
}
// Perform this one time only
dispatch_once(&Static.token)
{
let originalSelector = #selector(TestSwizzling.methodOne)
let swizzledSelector = #selector(TestSwizzling.methodTwo)
let originalMethod = class_getInstanceMethod(self, originalSelector);
let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
func methodTwo()->Int{
// It will not be a recursive call anymore after the swizzling
return methodTwo()+1
}
}
var c = TestSwizzling()
print(c.methodOne())
print(c.methodTwo())
If you need an example to play with, check out this sample project on github.

Resources