skip to Main Content

I have an internal class that adoptsUIScrollViewDelegate, but does not implement scrollViewWillEndDragging(_:withVelocity:targetContentOffset:). I want to add this method to the class using class_addMethod, but unfortunately run into a EXC_BAD_ACCESS error when trying to assign a new value to targetContentOffset.

Here is my code:

func extendDelegate(scrollViewDelegte: UIScrollViewDelegate) {
   let block : @convention(block) (UIScrollView, CGPoint, UnsafeMutablePointer<CGPoint>) -> Void = { scrollView, velocity, targetContentOffset  in
      // EXC_BAD_ACCESS here. 
      // I can read the pointee and it does contain a valid CGPoint value, 
      // however assigning to it gives a crash.
      targetContentOffset.pointee = .zero
   }

   class_addMethod(
      type(of: scrollViewDelegte),
      #selector(UIScrollViewDelegate.scrollViewWillEndDragging(_:withVelocity:targetContentOffset:)),
      imp_implementationWithBlock(block),
      "v@:@{CGPoint=dd}^{CGPoint=dd}"
   )
}

Any helps is greatly appreciated!

2

Answers


  1. From imp_implementationWithBlock‘s documentation:

    The signature of block should be method_return_type ^(id self, method_args ...)

    I think you are missing self in parameters.

    Login or Signup to reply.
  2. Rather than generating your own IMP and type encoding, I’d suggest you let the compiler do it for you, by creating a method normally, and transplanting it over:

    import Foundation
    import ObjectiveC
    
    @objc class ScrollViewPatch: NSObject {
        @objc func scrollViewWillEndDragging(
            _ scrollView: UIScrollView,
            withVelocity velocity: CGPoint,
            targetContentOffset: UnsafeMutablePointer<CGPoint>
        ) {
            targetContentOffset.pointee = .zero
        }
    }
    
    func extendDelegate(scrollViewDelegte: UIScrollViewDelegate) {
        let patchedMethod = class_getInstanceMethod(
            ScrollViewPatch.self,
            #selector(ScrollViewPatch.scrollViewWillEndDragging(_:withVelocity:targetContentOffset:))
        )!
    
        class_addMethod(
            type(of: scrollViewDelegte),
            #selector(UIScrollViewDelegate.scrollViewWillEndDragging(_:withVelocity:targetContentOffset:)),
            method_getImplementation(patchedMethod),
            method_getTypeEncoding(patchedMethod) // Much more reliable than hard-coding "v@:@{CGPoint=dd}^{CGPoint=dd}"
        )
    }
    

    Here’s a simpler standalone proof-of-concept:

    import Foundation
    import ObjectiveC
    
    @objc class MethodSource: NSObject {
        @objc func myMethod(_ arg: NSString) {
            print("Called (#function) on (self) with "(arg)"")
        }
    }
    
    @objc class TargetClass: NSObject {
        // Doens't natively respond to `-myMethod`
    }
    
    let targetObject = TargetClass()
    
    // The target object doesn't initially respond to `-myMethod`
    print(targetObject.responds(to: #selector(MethodSource.myMethod))) // false
    
    let patchedMethod = class_getInstanceMethod(
        MethodSource.self,
        #selector(MethodSource.myMethod)
    )!
    
    class_addMethod(
        type(of: targetObject),
        #selector(MethodSource.myMethod),
        method_getImplementation(patchedMethod),
        method_getTypeEncoding(patchedMethod)
    )
    
    // Now it's true
    print(targetObject.responds(to: #selector(MethodSource.myMethod)))
    
    // Called myMethod(_:) on <main.TargetClass: 0x6000016b0040> with "some string"
    targetObject.perform(#selector(MethodSource.myMethod), with: "some string")
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search