具有内部委托的 UICollectionView 子类在外部委托未实现相同方法时不会被触发

问题描述 投票:0回答:2

我正在为 swift 开发第 3 方框架,所以我不能自己使用

UICollectionViewDelegate
的委托方法,但我确实需要它们来实现一些自定义逻辑。

尝试了多种方法让它发挥作用,包括 method swizzling 但最后我觉得它对我正在做的事情来说太老套了。

现在我正在子类化

UICollectionView
并将委托设置为内部(我的)委托。 这很好用,除非
UIViewController
没有实现该方法。

现在我的代码是这样的:

fileprivate class UICollectionViewDelegateInternal: NSObject, UICollectionViewDelegate {
    var userDelegate: UICollectionViewDelegate?
    
    override func responds(to aSelector: Selector!) -> Bool {
        return super.responds(to: aSelector) || userDelegate?.responds(to: aSelector) == true
    }
    
    override func forwardingTarget(for aSelector: Selector!) -> Any? {
        if userDelegate?.responds(to: aSelector) == true {
            return userDelegate
        }
        return super.forwardingTarget(for: aSelector)
    }
    
    func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
        let collection = collectionView as! CustomCollectionView
        collection.didEnd(item: indexPath.item)
        userDelegate?.collectionView?(collectionView, didEndDisplaying: cell, forItemAt: indexPath)
    }
}

class CustomCollectionView: UICollectionView {
    private let internalDelegate: UICollectionViewDelegateInternal = UICollectionViewDelegateInternal()
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        super.delegate = internalDelegate
    }
    
    override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
        super.init(frame: frame, collectionViewLayout: layout)
        super.delegate = internalDelegate
    }

    
    
    func didEnd(item: Int) {
        print("internal - didEndDisplaying: \(item)")
    }
    
    
    override var delegate: UICollectionViewDelegate? {
        get {
            return internalDelegate.userDelegate
        }
        set {
            self.internalDelegate.userDelegate = newValue
            super.delegate = nil
            super.delegate = self.internalDelegate
        }
    }
}

ViewController
中,我只是使用委托方法进行了简单设置
didEndDisplaying
未实现

是否可以在没有实现 ViewController 的情况下收听

didEndDisplaying

编辑1:

这是 ViewController 的代码,让我更清楚我在做什么

class ViewController: UIViewController {
    @IBOutlet weak var collectionView: CustomCollectionView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        collectionView.dataSource = self
        collectionView.delegate = self
    }
}


extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        1000
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
                let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
                cell.backgroundColor = .blue
                return cell
    }
    
//    func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
//        print("view controller - did end displaying: \(indexPath.item)")
//    }
}

didEndDisplaying
CustomCollectionView
仅在我取消注释
didEndDisplaying
中的
ViewController
方法时触发。

我正在寻找的是如果

didEndDisplaying
中的
CustomCollectionView
方法未实现,则也触发
didEndDisplaying
ViewController

希望现在更清楚一点

编辑2:

发现上面的代码有一些错误,导致复制无法按我的预期进行。更新了上面的代码。 还制作了一个github页面,以便于在这里重现: https://github.com/mees-vdb/InternalCollectionView-Delegate

ios swift uicollectionview delegates
2个回答
3
投票

我对这种方法做了一些阅读,似乎它应该有效 - 但显然,它没有。

玩了一点,这 might 是你的解决方案。

我对您现有的代码做了很少的更改(从 GitHub 上获取 - 如果您想将我添加为协作者,我可以推送一个新分支 [GitHub 上的相同 DonMag ID])。

首先,我实施了

didSelectItemAt
以使其更容易调试(一次只有一个事件)。

ViewController类

class ViewController: UIViewController {
    @IBOutlet weak var collectionView: CustomCollectionView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        collectionView.dataSource = self
        collectionView.delegate = self
    }
}


extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        1000
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
        cell.backgroundColor = .blue
        return cell
    }
    
    // DonMag - comment / un-comment these methods
    //  to see the difference
    
    //func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    //  print("view controller - did end displaying: \(indexPath.item)")
    //}
    
    //func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    //  print("view controller - didSelectItemAt", indexPath)
    //}
}

UICollectionViewDelegate内部类

fileprivate class UICollectionViewDelegateInternal: NSObject, UICollectionViewDelegate {
    var userDelegate: UICollectionViewDelegate?
    
    override func responds(to aSelector: Selector!) -> Bool {
        return super.responds(to: aSelector) || userDelegate?.responds(to: aSelector) == true
    }
    
    override func forwardingTarget(for aSelector: Selector!) -> Any? {
        if userDelegate?.responds(to: aSelector) == true {
            return userDelegate
        }
        return super.forwardingTarget(for: aSelector)
    }
    
    func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
        let collection = collectionView as! CustomCollectionView
        collection.didEnd(item: indexPath.item)
        userDelegate?.collectionView?(collectionView, didEndDisplaying: cell, forItemAt: indexPath)
    }
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        let collection = collectionView as! CustomCollectionView
        collection.didSel(p: indexPath)
        userDelegate?.collectionView?(collectionView, didSelectItemAt: indexPath)
    }
    
}

CustomCollectionView 类

// DonMag - conform to UICollectionViewDelegate
class CustomCollectionView: UICollectionView, UICollectionViewDelegate {
    private let internalDelegate: UICollectionViewDelegateInternal = UICollectionViewDelegateInternal()
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        super.delegate = internalDelegate
    }
    
    override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
        super.init(frame: frame, collectionViewLayout: layout)
        super.delegate = internalDelegate
    }
    
    func didEnd(item: Int) {
        print("internal - didEndDisplaying: \(item)")
    }
    
    func didSel(p: IndexPath) {
        print("internal - didSelectItemAt", p)
    }
    
    // DonMag - these will NEVER be called,
    //  whether or not they're implemented in
    //  UICollectionViewDelegateInternal and/or ViewController
    // but, when implemented here,
    //  it allows (enables?) them to be called in UICollectionViewDelegateInternal
    func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
        print("CustomCollectionView - didEndDisplaying", indexPath)
    }
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        print("CustomCollectionView - didSelectItemAt", indexPath)
    }
    
    override var delegate: UICollectionViewDelegate? {
        get {
            // DonMag - return self instead of internalDelegate.userDelegate
            return self
            //return internalDelegate.userDelegate
        }
        set {
            self.internalDelegate.userDelegate = newValue
            super.delegate = nil
            super.delegate = self.internalDelegate
        }
    }
}

0
投票

根据您的喜好,这里有一种更简洁的方法来拦截 UIViewController 中的委托:

第 1 部分,我以“其他”方式进行。自然代表是您的代表,您跟踪“外部”代表。

在这个例子中,我拦截了

delegate
dataSource
,但我认为你可以根据需要只拦截一个或另一个。

class TrickCollection: UICollectionView, UICollectionViewDelegate, UICollectionViewDataSource {
    
    weak var outsideDelegate: UICollectionViewDelegate?
    weak var outsideDataSource: UICollectionViewDataSource?
    
    override var delegate: UICollectionViewDelegate? {
        
        set { outsideDelegate = newValue }
        get { return outsideDelegate }
    }
    
    override var dataSource: UICollectionViewDataSource? {
        
        set { outsideDataSource = newValue }
        get { return outsideDataSource }
    }
    

第 2 部分,在 bringup 中,劫持代表。

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        common()
    }
    
    override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
        super.init(frame: frame, collectionViewLayout: layout)
        common()
    }
    
    func common() {
        super.delegate = self
        super.dataSource = self
    }
    

第 3 部分,您现在拥有对代表的完全权力。

然而重要的是要意识到......见下文......

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        
        let k = outsideDataSource?.collectionView(self, numberOfItemsInSection: section) ?? 0
        print("yo, i intercepted the count \(k)")
        return k
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        
        let cell = outsideDataSource?.collectionView(self, cellForItemAt: indexPath) as! YourCellClass
        print("yo, i intercepted a cell \(cell.yourData?.headline)")
        return cell
    }

然而,重要的是要意识到这是一个“你拥有完全控制权”的设置。 除非您希望,否则不会将任何内容传递给傻瓜“外部”代表。

对我个人来说,其实很懂事很酷。 TrickCollection 不是 UIViewCollection,它是不同的。消费程序员(可能是你自己 :))不应该期望它的行为相同。

不要忘记,从哲学上讲,您在任何时候都输入过像“layoutSubviews”这样常见的东西,您可以选择是否调用 super-layoutSubviews,而且至关重要的是,您在代码中的什么时候调用它.

所以对我来说这不是问题,事实上它是正确的,在此处给出的方法中,所有委托调用在上游都不可用(除非您专门这样做)。

权衡的结果是代码简单而干净。

请注意,这确实与...相同https://stackoverflow.com/a/75997746/294884

如果这种方法对任何人都很方便,那就太好了!

关于集合视图/表视图。我发现,实际上,在现实世界中我从来没有必要! 以这种方式拦截集合/表视图。当使用集合视图时,应用程序不可避免地需要与运动同步的“其他东西”,并且您不想弄乱上游的消费者代码。无论如何。

为方便起见,将整个内容复制/粘贴。

class TrickCollection: UICollectionView, UICollectionViewDelegate, UICollectionViewDataSource {
    
    weak var outsideDelegate: UICollectionViewDelegate?
    weak var outsideDataSource: UICollectionViewDataSource?
    
    override var delegate: UICollectionViewDelegate? {
        
        set { outsideDelegate = newValue }
        get { return outsideDelegate }
    }
    
    override var dataSource: UICollectionViewDataSource? {
        
        set { outsideDataSource = newValue }
        get { return outsideDataSource }
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        common()
    }
    
    override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
        super.init(frame: frame, collectionViewLayout: layout)
        common()
    }
    
    func common() {
        super.delegate = self
        super.dataSource = self
    }
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        
        let k = outsideDataSource?.collectionView(self, numberOfItemsInSection: section) ?? 0
        print("yo, i intercepted the count \(k)")
        return k
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        
        let cell = outsideDataSource?.collectionView(self, cellForItemAt: indexPath) as! YourCellClass
        print("yo, i intercepted a cell \(cell.yourData?.headline)")
        return cell
    }
}

同样,您通常只想要两个代表中的一个。

© www.soinside.com 2019 - 2024. All rights reserved.