Nirri Nirri - 4 months ago 53
Swift Question

Multiple delegates for UITableView in swift

I think it's best to illustrate what I'm trying to achieve with a concrete example:

Let's say I'm making a cocoapod.
I have a table view inside the example project, and I want the tableview's delegate methods for its scrolling to be called inside my cocoapod. I know how to pass the tableView into my cocoapod module, but I'm not sure how I can listen to delegate methods inside it, since I want my ViewController in the example app to also be a delegate.

End result is, I want to show a simple view going up and down with the scrollbar on the tableView when it scrolls.

First of all, which scrollview delegate methods should I use so I can update the y position of my custom view at all times to match the center of the scrollbar's y position?

Second of all, how can I listen to them (scrollview/tableview delegate methods) inside my cocoapod module?

Note: I'm using Swift 2.2

Answer

You can achieve both goals by using Key-Value Observing (KVO), to monitor the contentOffset and the contentSize of the scroll view independently of the table view delegate.

The contentOffset is the amount that the scrollView has scrolled. The y value is the amount scrolled in the vertical direction.

The contentSize is the total height of all of the table rows.

KVO lets you write code which gets called whenever a property on another object changes. You can use KVO to monitor changes to contentSize and contentOffset, and update the custom view when those values change.

Here is how you might implement it in your CocoaPod:

private var ContentOffsetKVO = 0
private var ContentSizeKVO = 0

public class ScrollController: NSObject {

    public var customView: UIView?

    public var scrollView: UIScrollView? {
        didSet {
            if let view = oldValue {
                removeKVO(view)
            }

            if let view = scrollView {
                addKVO(view)
                updateScrollPosition(view)
            }
        }
    }

    private func removeKVO(scrollView: UIScrollView) {

        scrollView.removeObserver(
            self,
            forKeyPath: "contentSize",
            context: &ContentSizeKVO
        )

        scrollView.removeObserver(
            self,
            forKeyPath: "contentOffset",
            context: &ContentOffsetKVO
        )
    }

    private func addKVO(scrollView: UIScrollView) {

        scrollView.addObserver(
            self,
            forKeyPath: "contentSize",
            options: [.Initial, .New],
            context: &ContentSizeKVO
        )

        scrollView.addObserver(
            self,
            forKeyPath: "contentOffset",
            options: [.Initial, .New],
            context: &ContentOffsetKVO
        )
    }

    public override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {

        switch keyPath {

        case .Some("contentSize"), .Some("contentOffset"):
            if let scrollView = object as? UIScrollView {
                NSOperationQueue.mainQueue().addOperationWithBlock() { [weak self] in
                    self?.updateScrollPosition(scrollView)
                }
            }

        default:
            super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
        }
    }

    private func updateScrollPosition(scrollView: UIScrollView) {

        guard let customView = customView else {
            return
        }

        // Update with the custom view with new scroll position.

        let viewSize = scrollView.bounds.size.height
        let scrollLimit = scrollView.contentSize.height - viewSize
        let scrollOffset = scrollView.contentOffset.y
        let scrollBarPosition = (scrollOffset / scrollLimit) * viewSize

        customView.center = CGPoint(
            x: 0,
            y: -scrollOffset + scrollBarPosition
        )

    }
}

To use this:

class MyTableViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let scrollController = ScrollController()

        // UITableView is a sub-class of UIScrollView so we can assign it directly.
        scrollController.scrollView = tableView

        let customView = UIView(frame: ...)
        scrollViewController.customView = customView
        tableView.addSubview(customView)
    }
}
Comments