iosobjective-cswiftuiimagemethod-swizzling

Swizzling UIImage init not working iOS Swift


I am trying to swizzle UIImage.init(named:) but the init is not being called

extension UIImage {

    @objc public convenience init?(swizzledName: String) {
        self.init(named: swizzledName)

        /// Do something
        print("this is working")
    }

    static func swizzle() {
        guard let instance = class_getClassMethod(self, #selector(UIImage.init(named:))),
            let swizzledInstance = class_getClassMethod(self, #selector(UIImage.init(swizzledName:))) else { return }

        method_exchangeImplementations(instance, swizzledInstance)
    }
}

Usage

UIImage.swizzle()
let image = UIImage(named: "avatar")

👆 not working


Solution

  • I too was having trouble swizzling these init methods for UIImage. The only way I found was to instead use class methods, which seem to work fine (I first tried with static methods, but that didn't work, so then I tried class methods, which did work):

    func swizzle(originalClass: AnyClass,
                 originalSelector: Selector,
                 isOriginalSelectorClassMethod: Bool,
                 swizzledClass: AnyClass,
                 swizzledSelector: Selector,
                 isSwizzledSelectorClassMethod: Bool) {
        guard let originalMethod = isOriginalSelectorClassMethod ?
            class_getClassMethod(originalClass, originalSelector) :
            class_getInstanceMethod(originalClass, originalSelector) else {
                return
        }
    
        guard let swizzledMethod = isSwizzledSelectorClassMethod ?
            class_getClassMethod(swizzledClass, swizzledSelector) :
            class_getInstanceMethod(swizzledClass, swizzledSelector) else {
                return
        }
    
        let didAddMethod = class_addMethod(isOriginalSelectorClassMethod ? object_getClass(originalClass)! : originalClass,
                                           originalSelector,
                                           method_getImplementation(swizzledMethod),
                                           method_getTypeEncoding(swizzledMethod))
    
        if didAddMethod {
            class_replaceMethod(isSwizzledSelectorClassMethod ? object_getClass(swizzledClass)! : swizzledClass,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod))
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod)
        }
    }
    
    extension UIImage {
        static func swizzleInitializersIfNeeded() {
            guard !areInitializersSwizzled else {
                return
            }
    
            areInitializersSwizzled = true
    
            swizzle(originalClass: self,
                    originalSelector: #selector(UIImage.init(named:)),
                    isOriginalSelectorClassMethod: true,
                    swizzledClass: self,
                    swizzledSelector: #selector(UIImage.image(named:)),
                    isSwizzledSelectorClassMethod: true)
    
            swizzle(originalClass: self,
                    originalSelector: #selector(UIImage.init(named:in:with:)),
                    isOriginalSelectorClassMethod: true,
                    swizzledClass: self,
                    swizzledSelector: #selector(UIImage.image(named:in:with:)),
                    isSwizzledSelectorClassMethod: true)
        }
    
        private static var areInitializersSwizzled = false
    
        @objc fileprivate class func image(named name: String) -> UIImage? {
            let image = self.image(named: name)
            image?.name = name
    
            return image
        }
    
        @objc fileprivate class func image(named name: String,
                                           in bundle: Bundle,
                                           with config: UIImage.Configuration) -> UIImage? {
            let image = self.image(named: name, in: bundle, with: config)
            image?.name = name
            image?.bundle = bundle
    
            return image
        }
    
        private static var nameKey = 0
        private static var bundleKey = 0
    
        private(set) var name: String? {
            get { objc_getAssociatedObject(self, &UIImage.nameKey) as? String }
            set { objc_setAssociatedObject(self, &UIImage.nameKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
        }
    
        private(set) var bundle: Bundle? {
            get { objc_getAssociatedObject(self, &UIImage.bundleKey) as? Bundle }
            set { objc_setAssociatedObject(self, &UIImage.bundleKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
        }
    }
    
    class ViewController: UIViewController {
        override func viewDidLoad() {
            super.viewDidLoad()
    
            UIImage.swizzleInitializersIfNeeded()
    
            let image = UIImage(named: "test_image")
    
            print(image?.name as Any) // prints Optional("test_image")
        }
    }