在 UICollectionView 子类中实现 UIScrollViewDelegate

Posted

技术标签:

【中文标题】在 UICollectionView 子类中实现 UIScrollViewDelegate【英文标题】:implementing UIScrollViewDelegate in a UICollectionView subclass 【发布时间】:2018-11-10 23:22:31 【问题描述】:

我有一个UICollectionView 子类。

我想从UIScrollViewDelegate添加scrollViewDidScroll的默认实现

有没有办法从 UICollectionView 子类访问 scrollView 委托方法?

谢谢

【问题讨论】:

我也想知道 【参考方案1】:

你可以添加一个实现你需要的默认代码的函数,例如:

class YourCollectionView: UICollectionView 

    override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) 
        super.init(frame: frame, collectionViewLayout: layout)
    

    required init?(coder aDecoder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    

    func updateUIOnScrollViewDidScroll(_ scrollView: UIScrollView) 
        //...
    

然后当你在视图控制器中实现委托函数时,添加:

func scrollViewDidScroll(_ scrollView: UIScrollView) 
    yourCollectionView.updateUIOnScrollViewDidScroll(scrollView)

编辑

如果你想像外部库一样使用你的集合并且你不想每次都调用更新的函数,你可以实现一个只符合 UICollectionViewDelegate 的自定义类(如果你想要你可以有一个单独的 CustomDataSource实现数据源和委托的类),例如:

class YourCollectionViewDelegate: NSObject, UICollectionViewDelegate 
    // implement a callback for every function you need to manage in the view controller 
    var onSelectedItemAt: ((IndexPath) -> Void)?
    func collectionView(_ collectionView: UICollectionView, 
         didSelectItemAt indexPath: IndexPath) 
        onSelectedItemAt?(indexPath)


func scrollViewDidScroll(_ scrollView: UIScrollView) 
     guard let collectionView = scrollView as? YourCollectionViewClass else  fatalError(“your message”) 
    // implement your ui update 

然后在您的视图控制器中,您只需将委托与您的视图控制器绑定:

class MyViewController: UIViewController 

    //...
    let customDelegate = YourCollectionViewDelegate()

    override func viewDidLoad() 
         super.viewDidLoad()
        //...
        myCollection.delegate = customDelegate
        setupBindings()
    

    private func setupBindings() 

        customDelegate.onSelectedItemAt =  [weak self] indexPath in 
            //...
        
    

【讨论】:

谢谢,我有一个 CollectionView,它的某些功能在所有应用程序中都是相同的。我希望它像外部库一样工作。 不想在每个拥有这个collectionview的viewcontroller中调用更新函数 我编辑了我的答案;)让我知道这是否适合你。【参考方案2】:

每当用户滚动它时,我们可以使用delegation 概念从CustomCollectionView 获取访问权限。

这是CustomCollectionView 的实现,还要注意delegate 方法是optional,在Objective C 中可用。

因此,如果您的 ViewControllerCustomCollectionViewDelegate protocol 确认,则它需要实现委托方法,否则它不必。

注意:

但是,CustomCollectionViewUICollectionView 的子类,表示其简单的通用 UI 元素。其实就是Model-View-Controller (MVC)中的View。根据MVCView不能直接与Controller通信,ViewController之间的通信是blind & structured。这种通信的典型例子是Target & Actiondelegate 模式。

委托是一个简单的变量,它包含在UIScrollView, UITableView, UICollectionView等通用UI组件中。Controller必须通过将UI元素的delegate设置为self来确认协议才能实现里面的委托方法。

结论是通用 UI 元素的子类无法实现其中的委托方法。

但是,我们可以通过使用XIB 制作自定义的UIView 并将collectionView 添加到其中来实现。

代码:

CustomCollectionView:

import UIKit

@objc protocol CustomCollectionViewDelegate 

    @objc optional func collectionViewDidScroll(_ scrollView: UIScrollView)


class CustomCollectionView: UIView 

    //MARK: - Outlets
    @IBOutlet weak var collection: UICollectionView!

    //MARK: - Variables
    weak var vc: UIViewController!
    weak var view: UIView!
    weak var customDelegate: CustomCollectionViewDelegate?

    let titles = ["HorizontalCollectionView", "VerticalCollectionView"]

    required init?(coder aDecoder: NSCoder) 
        super.init(coder: aDecoder)
    

    init(frame: CGRect, in vc: UIViewController, setCustomDelegate set: Bool) 
        super.init(frame: frame)
        xibSetup(frame: CGRect(x: 0, y: 0, width: frame.width, height: frame.height))
        self.vc = vc
        self.customDelegate = set ? vc as? CustomCollectionViewDelegate : nil
    

    override init(frame: CGRect) 
        super.init(frame: frame)
        xibSetup(frame: CGRect(x: 0, y: 0, width: frame.width, height: frame.height))
    

    private func xibSetup(frame: CGRect) 

        view = loadViewFromNib()
        view.frame = frame
        addSubview(view)

        collection.register(UINib(nibName: "CustomCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "CustomCollectionViewCell")
        collection.delegate = self
        collection.dataSource = self
    

    private func loadViewFromNib() -> UIView 

        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: "CustomCollectionView", bundle: bundle)
        let view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView

        return view
    



extension CustomCollectionView: UIScrollViewDelegate 

    func scrollViewDidScroll(_ scrollView: UIScrollView) 

        if customDelegate != nil 
            customDelegate!.collectionViewDidScroll!(scrollView)
        
    


extension CustomCollectionView: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout 

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int 

        return titles.count
    

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets 

        return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
    

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat 

        return 0
    

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat 

        return 10 // Adjust the inter item space based on the requirement.
    

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize 

        return CGSize(width: 300, height: collectionView.bounds.height)
    

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 

        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCollectionViewCell", for: indexPath) as! CustomCollectionViewCell
        cell.titleLabel.text = titles[indexPath.row]
        return cell
    

CustomCollectionView XIB:

<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14313.18" targetRuntime="ios.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
    <device id="retina4_7" orientation="portrait">
        <adaptation id="fullscreen"/>
    </device>
    <dependencies>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14283.14"/>
        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <objects>
        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="CustomCollectionView" customModule="SampleDemoApp" customModuleProvider="target">
            <connections>
                <outlet property="collection" destination="loF-CI-n5C" id="EZi-It-39z"/>
            </connections>
        </placeholder>
        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
        <view contentMode="scaleToFill" id="iN0-l3-epB" customClass="CustomCollectionView" customModule="SampleDemoApp" customModuleProvider="target">
            <rect key="frame" x="0.0" y="0.0"  />
            <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
            <subviews>
                <collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="none" translatesAutoresizingMaskIntoConstraints="NO" id="loF-CI-n5C">
                    <rect key="frame" x="0.0" y="0.0"  />
                    <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                    <collectionViewFlowLayout key="collectionViewLayout" scrollDirection="horizontal" minimumLineSpacing="10" minimumInteritemSpacing="10" id="HQB-uW-7CY">
                        <size key="itemSize"  />
                        <size key="headerReferenceSize"  />
                        <size key="footerReferenceSize"  />
                        <inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
                    </collectionViewFlowLayout>
                </collectionView>
            </subviews>
            <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
            <constraints>
                <constraint firstItem="loF-CI-n5C" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="Khs-Aw-6b7"/>
                <constraint firstItem="loF-CI-n5C" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="cEr-al-Pib"/>
                <constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="loF-CI-n5C" secondAttribute="bottom" id="ftp-QG-OGJ"/>
                <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="loF-CI-n5C" secondAttribute="trailing" id="num-9n-spN"/>
            </constraints>
            <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
            <point key="canvasLocation" x="138.40000000000001" y="153.37331334332833"/>
        </view>
    </objects>
</document>

视图控制器:

override func viewDidLoad() 
    super.viewDidLoad()

    let collectionView = CustomCollectionView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 200), in: self, setCustomDelegate: true)
    view.addSubview(collectionView)

委托实施:

extension ViewController: CustomCollectionViewDelegate 

    func collectionViewDidScroll(_ scrollView: UIScrollView) 

        //do something...
    

【讨论】:

你没有设置 UIScrolllViewDelegate 所以 scrollViewDidScroll 不会被调用 那是我的错。对不起..!!在多次击中我的头之后,我开始知道按照 MVC 向通用 UI 元素添加委托方法是不可能的。但是可以通过自定义 UIView 来实现。我已经更新了我的答案,请看一下。

以上是关于在 UICollectionView 子类中实现 UIScrollViewDelegate的主要内容,如果未能解决你的问题,请参考以下文章

如何在 UICollectionView 中实现页面之间的间距?

在 UICollectionView 中实现按钮点击

使用 Xib 创建 UICollectionView/UICollectionViewController 组件并在 UIViewController 中实现

如何使用 Swift 3 for iOS 在 ViewController.swift 中实现 UICollectionView

在子类的子类中实现 NSCopying

强制在子类中实现 toString()