如何滚动到给定的 indexPath.item?

Posted

技术标签:

【中文标题】如何滚动到给定的 indexPath.item?【英文标题】:How to scroll to a given indexPath.item? 【发布时间】:2021-04-17 17:59:25 【问题描述】:

我有 2 个并行的集合视图。其中一个是菜单,另一个是包含垂直表视图的大菜单,其中填充了 API 查询数据。

当用户向右滚动时,Order History 会突出显示,并且底部大集合视图中的下一个单元格会占据屏幕的其余部分,并显示自己的 tableview 数据。就像implementation here。

除了我在附图所示状态下点击订单历史记录时,一切正常,底部的集合视图不会滚动到大集合视图中的第二个单元格。如果我点击当前订单单元格,它会滚动到大集合视图中的第一个单元格。在过去的 48 小时内,我不知道是什么导致了这个问题。

这是菜单栏:

class MenuBar: UIView, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout 
    
    lazy var menuCollection: UICollectionView = 
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .horizontal
        let collectionview = UICollectionView(frame: .zero, collectionViewLayout: layout)
        collectionview.translatesAutoresizingMaskIntoConstraints = false
        collectionview.backgroundColor = .white
        collectionview.delegate = self
        collectionview.dataSource = self
        collectionview.register(InvoiceCell.self, forCellWithReuseIdentifier: InvoiceCell.identifier)
        return collectionview
    ()
    
    var leftIndicatorConstraint: NSLayoutConstraint?
    private let menuOptions = ["Current Orders", "Order History"]
    var containerViewController: ContainerViewController? // I use an instance of the parent VC to pass on the value
    
    override init(frame: CGRect) 
        super.init(frame: frame)
        
        addSubview(menuCollection)
        menuCollection.topAnchor.constraint(equalTo: self.topAnchor, constant: 0).isActive = true
        menuCollection.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0).isActive = true
        menuCollection.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 0).isActive = true
        menuCollection.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 0).isActive = true
        menuCollection.selectItem(at: IndexPath(row: 0, section: 0), animated: true, scrollPosition: .left)
        setupIndicator()
    
    
    required init?(coder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    
    
    func setupIndicator() 
        let indicatorBar = UIView()
        indicatorBar.backgroundColor = UIColor(hex: Constants.Colors.primary)
        addSubview(indicatorBar)
        indicatorBar.translatesAutoresizingMaskIntoConstraints = false
        leftIndicatorConstraint = indicatorBar.leadingAnchor.constraint(equalTo: self.leadingAnchor)
        leftIndicatorConstraint?.isActive = true
        indicatorBar.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
        indicatorBar.heightAnchor.constraint(equalToConstant: 5).isActive = true
        indicatorBar.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.5).isActive = true
    
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) 
        containerViewController?.scrollToMenu(menuIndex: indexPath.item) // Here I pass on the value of the menu item that's tapped
    
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int 
        return menuOptions.count
    
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: InvoiceCell.identifier, for: indexPath) as? InvoiceCell else  return UICollectionViewCell() 
        cell.thisOption = menuOptions[indexPath.row]
        return cell
    
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize 
        return CGSize(width: menuCollection.frame.size.width / CGFloat(menuOptions.count), height: menuCollection.frame.size.height)
    
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat 
        return 0
    
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat 
        return 0
    

这是包含菜单集合视图以及大基础集合视图的父 UIView 控制器

class ContainerViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout 
    
    lazy var menuBar: MenuBar = 
        let menuBar = MenuBar()
        menuBar.containerViewController = self
        menuBar.translatesAutoresizingMaskIntoConstraints = false
        return menuBar
    ()
    
    lazy var baseCollectionView: UICollectionView = 
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .horizontal
        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
        collectionView.register(PastCell.self, forCellWithReuseIdentifier: PastCell.pastCellId)
        collectionView.register(CurrentCell.self, forCellWithReuseIdentifier: CurrentCell.currentCellId)
        collectionView.isPagingEnabled = true
        collectionView.delegate = self
        collectionView.dataSource = self
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        collectionView.showsHorizontalScrollIndicator = false
        collectionView.backgroundColor = .white
        return collectionView
    ()
    
    var orderViewModel = OrderViewModel(order: Order(addressOne: "", addressTwo: "", city: "", postalCode: "", mpName: "", planType: 0, restaurantAddress: "", restaurantId: "", restaurantName: "", timeOfCreation: nil, currentTotal: 0.00, orderMenu: [], status: 0, itemshtml: ""))
    
    override func viewDidLoad() 
        super.viewDidLoad()
        
        view.backgroundColor = .white
        setupView()
    
    
    override func viewDidDisappear(_ animated: Bool) 
        super.viewDidDisappear(animated)
        orderViewModel.removeListener()
    
    
    private func setupView() 
        view.addSubview(menuBar)
        menuBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
        menuBar.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
        menuBar.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true
        menuBar.heightAnchor.constraint(equalToConstant: 40).isActive = true
        
        view.addSubview(baseCollectionView)
        baseCollectionView.topAnchor.constraint(equalTo: menuBar.bottomAnchor, constant: 0).isActive = true
        baseCollectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
        baseCollectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
        baseCollectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
    
    
    func scrollToMenu(menuIndex: Int) 
        // Here I am using the passed on menu index to scroll to the appropriate cell in the big collection view.
        let indexPath = IndexPath(item: menuIndex, section: 0)
        baseCollectionView.scrollToItem(at: indexPath, at: .left, animated: true) // For some reason it ALWAYS SCROLL TO THE FIRST CELL    
    
    func scrollViewDidScroll(_ scrollView: UIScrollView) 
        menuBar.leftIndicatorConstraint?.constant = scrollView.contentOffset.x / 2
    
    
    func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) 
        let index = Int(targetContentOffset.pointee.x / view.frame.width)
        menuBar.menuCollection.selectItem(at: IndexPath(item: index, section: 0), animated: true, scrollPosition: .left)
    
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int 
        return 2
    
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 
        switch indexPath.row 
        case 0:
            if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CurrentCell.currentCellId, for: indexPath) as? CurrentCell 
                if let tabBarHeight = self.tabBarController?.tabBar.frame.size.height 
                    cell.adjustSize(top: menuBar.frame.height * 1.2, bottom: tabBarHeight * 0.5)
                    return cell
                
            
        case 1:
            if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PastCell.pastCellId, for: indexPath) as? PastCell 
                cell.backgroundColor = .systemOrange
                return cell
            
        default:
            return UICollectionViewCell()
        
        return UICollectionViewCell()
    
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize 
        return CGSize(width: view.frame.size.width, height: view.frame.size.height)
    
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat 
        return 0
    

【问题讨论】:

【参考方案1】:

原来有一个big bug with scrollToItem Apple 没有修复。在我的情况下,它是由 isPagingEnabled 引起的。所以the best solution I found was here 在滚动到项目之前禁用分页,然后再次启用它。

【讨论】:

以上是关于如何滚动到给定的 indexPath.item?的主要内容,如果未能解决你的问题,请参考以下文章

如何在屏幕底部加载集合视图?

UICollectionVIew indexPath.item/row 返回不正确的值

在 indexpath.item 以编程方式呈现 UIViewController(嵌入在 UITabBarController 中)

如何将 HTML 页面滚动到给定的锚点

如何从位于集合视图单元格内的步进器中获取选定的 IndexPath?

如何添加填充到搜索栏swift ios