0

I'm trying to make UITableView notify its delegate of contentSize when contentSize of UITableView is updated.

So I added property observer to contentSize of UITableview that calls delegation method named tableView(_ tableView: didUpdateContentSize contentSize:).

Then I extended UITableViewDelegate so that UIViewController conforming UITableViewDelegate can declare tableView(_ tableView: didUpdateContentSize contentSize:) for its use case.

extension UITableView {
    override open var contentSize: CGSize {
        didSet {
            self.delegate?.tableView(self, didUpdateContentSize: contentSize)
        }
    }
}

extension UITableViewDelegate {
    func tableView(_ tableView: UITableView, didUpdateContentSize contentSize: CGSize)
    // Error: "Expected '{' in body of function declaration"
}

But in extension code of UITableViewDelegate, it gets an error "Expected '{' in body of function declaration"

Is there no way to extend existing protocol (like UIKit delegate protocols) without function declaration?

I'd like to accomplish my intention without making subclass of UITableView.

Stleamist
  • 193
  • 2
  • 12
  • 1
    You need to write the method body. – iPeter Jul 30 '18 at 13:46
  • The error message is pretty clear: you missed typing the opening curly bracket (`{`) – Cristik Jul 30 '18 at 14:19
  • 1
    I just realised you are talking about a UIKit protocol. My answer was for custom methods and then you had to also declare the method in the main protocol. I think you'd be better with creating your own protocol and implement it from the controller. – regina_fallangi Jul 30 '18 at 14:26
  • @regina_fallangi Thanks for your answer! Can I create my own delegate protocol associated with `UITableView` itself, without creating subclass of `UITableView`? – Stleamist Jul 30 '18 at 14:30
  • @Cristik When I write method by in extension clause, how can I override that method in view controller conforms that protocol? – Stleamist Jul 30 '18 at 14:32
  • @Stleamist check my answer, let me know if that solves your problem. – Cristik Jul 30 '18 at 14:34

3 Answers3

2

This part is already invalid:

extension UITableView {
    override open var contentSize: CGSize {
        didSet {
            self.delegate?.tableView(self, didUpdateContentSize: contentSize)
        }
    }
}

It is not permitted to override a method/property in an extension. You may get away with it sometimes, but it is undefined behavior (and I'm surprised the Swift compiler doesn't throw an error). There is no promise that this implementation will actually be called. It might be; it might not be; it might sometimes be. See Overriding methods in Swift extensions for more details on that. (The fact that this is an NSObject subclass is probably why it's not failing, but that doesn't make it ok. This isn't safe in ObjC either.)

The correct way to implement this is to have the delegate use NSKeyValueObserving to observe the tableView's contentView.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • That's a good point for me. Now I have to find the way to uses KVO. Thanks for your professional answer! – Stleamist Jul 30 '18 at 14:54
1

If you absolutely need that default behaviour for all TableViews you have, you can do the following:

public protocol ContentSizeProtocol: UITableViewDelegate {
    func tableView(_ tableView: UITableView, didUpdateContentSize contentSize: CGSize)
}

public extension UITableView {
    override open var contentSize: CGSize {
        didSet {
            guard let delegate = delegate as? ContentSizeDelegate else {
                return
            }
            delegate.tableView(self, didUpdateContentSize: contentSize)
        }
    }
} 

You just have to make sure that whichever VC you have, they implement ContentSizeDelegate, and then you do tableView.delegate = viewControllerThatImplementsContentSizeDelegate.

regina_fallangi
  • 2,080
  • 2
  • 18
  • 38
  • Thank you for your help! :-) I think that downcasting delegate property to custom delegate is a key to the solution. – Stleamist Jul 30 '18 at 14:47
1

You cannot extend an existing protocol by adding new requirements (aka methods) to it. However you can derive the existing protocol, and add the new method to that protocol:

protocol MyTableViewDelegate: UITableViewDelegate {
    optional func tableView(_ tableView: UITableView, didUpdateContentSize contentSize: CGSize)
}

, and call this new method from the table view:

class MyTableView: UITableView {
    override open var contentSize: CGSize {
        didSet {
            (self.delegate as? MyTableViewDelegate)?.tableView?(self, didUpdateContentSize: contentSize)
        }
    }
}

Note that the above code subclasses UITableView instead of extending it, as overriding methods in extensions is not recommendable (though if you really want you can add the code in the extension over UITableView). This should not cause too many headaches, as the table view class can be easily changed in storyboards/xibs/code.

You can then have your view controller conform to the extended delegate, and you should be good to go:

extension MyViewController: MyTableViewDelegate {
    func tableView(_ tableView: UITableView, didUpdateContentSize contentSize: CGSize) {
        // do your stuff
    }
}
Cristik
  • 30,989
  • 25
  • 91
  • 127