Search code examples
iosswiftscrolluicollectionviewcell

Swift - Label moves to the upper left of the cell when scrolling the collection view


Each cell has a title with a label and it's supposed to be centered in the cell. When the view controller first appears each label is showed correctly, however when I scroll down the first cells move the label to the upper left and when I scroll up the last cells move the labels as well. How can I set each label to stay fixed in its initial position?

class TopicsVC: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UISearchBarDelegate {

override func viewDidLoad() {
self.filteredNames = self.names

    self.collectionView.register(SearchCollectionViewCell.self, forCellWithReuseIdentifier: "SearchCollectionViewCell.identifier")
    self.collectionView.register(TextCollectionViewCell.self, forCellWithReuseIdentifier: "TextCollectionViewCell.identifier")

    if let collectionViewLayout = self.collectionView.collectionViewLayout as? RSKCollectionViewRetractableFirstItemLayout {

        collectionViewLayout.firstItemRetractableAreaInset = UIEdgeInsets(top: 8.0, left: 0.0, bottom: 8.0, right: 0.0)
    }
}


 fileprivate let names = ["F", "N", "S", "M", "R", "T", "A", "M", "T", "F"]

fileprivate let images = ["f.png", "n.png", "s.png", "m.png", "r.png", "t.png", "a.png","m.png","t.png","f.png"]

// MARK: - Layout

internal override func viewDidLayoutSubviews() {

    super.viewDidLayoutSubviews()

    guard self.readyForPresentation == false else {

        return
    }

    self.readyForPresentation = true

    let searchItemIndexPath = IndexPath(item: 0, section: 0)
    self.collectionView.contentOffset = CGPoint(x: 0.0, y: self.collectionView(self.collectionView, layout: self.collectionView.collectionViewLayout, sizeForItemAt: searchItemIndexPath).height)
}

// MARK: - UICollectionViewDataSource

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

    switch indexPath.section {

    case 0:
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SearchCollectionViewCell.identifier", for: indexPath) as! SearchCollectionViewCell

        cell.searchBar.delegate = self
        cell.searchBar.searchBarStyle = .minimal
        cell.searchBar.placeholder = "Search a topic"

        return cell

    case 1:
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TextCollectionViewCell.identifier", for: indexPath) as! TextCollectionViewCell

        let name = self.filteredNames[indexPath.item]

        cell.colorView.layer.cornerRadius = 10.0
        cell.colorView.layer.masksToBounds = true
        //cell.colorView.backgroundColor = self.colors[name]


        cell.colorView.backgroundColor = UIColor(patternImage: UIImage(named: images[indexPath.row])!)
        cell.colorView.layer.contents = UIImage.init(named:images[indexPath.row])!.cgImage


        cell.label.textColor = UIColor.white
        cell.label.font = UIFont(name:"HelveticaNeue-Bold", size: 24.0)
        cell.label.sizeToFit()
        cell.label.numberOfLines = 2
        cell.label.textAlignment = .center
        cell.label.text = name

        return cell

    default:
        assert(false)
    }
}

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

    switch section {

    case 0:
        return 1

    case 1:
        return self.filteredNames.count

    default:
        assert(false)
    }
}

internal func numberOfSections(in collectionView: UICollectionView) -> Int {

    return 2
}

// MARK: - UICollectionViewDelegateFlowLayout

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

    switch section {

    case 0:
        return UIEdgeInsets.zero

    case 1:
        return UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0)

    default:
        assert(false)
    }
}

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

    return 10.0
}

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

    return 10.0
}

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

    switch indexPath.section {

    case 0:
        let itemWidth = collectionView.frame.width
        let itemHeight: CGFloat = 150.0

        return CGSize(width: itemWidth, height: itemHeight)

    case 1:
        let numberOfItemsInLine: CGFloat = 2

        let inset = self.collectionView(collectionView, layout: collectionViewLayout, insetForSectionAt: indexPath.section)
        let minimumInteritemSpacing = self.collectionView(collectionView, layout: collectionViewLayout, minimumInteritemSpacingForSectionAt: indexPath.section)

        let itemWidth = (collectionView.frame.width - inset.left - inset.right - minimumInteritemSpacing * (numberOfItemsInLine - 1)) / numberOfItemsInLine
        let itemHeight = itemWidth


        return CGSize(width: itemWidth, height: itemHeight)

    default:
        assert(false)
    }
}

// MARK: - UIScrollViewDelegate

internal func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {

    guard scrollView === self.collectionView else {

        return
    }

    let indexPath = IndexPath(item: 0, section: 0)
    guard let cell = self.collectionView.cellForItem(at: indexPath) as? SearchCollectionViewCell else {

        return
    }

    guard cell.searchBar.isFirstResponder else {

        return
    }

    cell.searchBar.resignFirstResponder()
}

// MARK: - UISearchBarDelegate

internal func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {

    let oldFilteredNames = self.filteredNames!

    if searchText.isEmpty {

        self.filteredNames = self.names
    }
    else {

        self.filteredNames = self.names.filter({ (name) -> Bool in

            return name.hasPrefix(searchText)
        })
    }

    self.collectionView.performBatchUpdates({

        for (oldIndex, oldName) in oldFilteredNames.enumerated() {

            if self.filteredNames.contains(oldName) == false {

                let indexPath = IndexPath(item: oldIndex, section: 1)
                self.collectionView.deleteItems(at: [indexPath])
            }
        }

        for (index, name) in self.filteredNames.enumerated() {

            if oldFilteredNames.contains(name) == false {

                let indexPath = IndexPath(item: index, section: 1)
                self.collectionView.insertItems(at: [indexPath])
            }
        }

    }, completion: nil)
}

Solution

  • It's possible that the code: cell.label.sizeToFit() inside your implementation of collectionView(_:, cellForItemAt:) is causing the problem. I recommend deleting that line.

    Reasoning: If you have already set up your label in Interface Builder to center the text in the cell, there's no need to update the label size here. Sizing to fit could cause the label to shrink, potentially resulting in the behavior you have described of seeming to move towards the top left.

    (Even worse, the call to sizeToFit() in your code above happens before you actually set the new text for the cell, so its results will be based on the text that was in the cell before it was reused.)