简体   繁体   中英

Issue updating constraint in an custom UICollectionView cell

I have an issue updating the with anchor of a constraints inside my collectionView cell. I have two views representing bars as percentages of eg the total amount of goals scored for the home and for the away team see the following picture for clarification .

When I look into the statistics the for the first time, everything works fine and I get the right print statements in my console (eg Width HomeCell: 139.0 and Width AwayCell: 27.0 for index 0). When I go back to my pitchViewController and add some more goals, I get an error and both bars disappear.

I already tried to call layoutIfNeeded() or setNeedsLayout() on both bar views. But it didn't worked so far.

Here is my console output, relevant code underneath:

Width HomeCell: 83.0
Width AwayCell: 83.0

[LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<NSLayoutConstraint:0x604000287440 UIView:0x7f870f461d50.width == 1   (active)>",
"<NSLayoutConstraint:0x60c00009a810 UIView:0x7f870f461d50.width == 83   (active)>"
)

Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x60c00009a810 UIView:0x7f870f461d50.width == 83   (active)>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.

My Custom Cell

import UIKit

class GameStatisticCell: BaseCell {

    let statisticTitleLabel: UILabel = {
        let label = UILabel()
        label.text = "Schüsse aufs Tor"
        label.textColor = ColorCodes.darkGray
        label.textAlignment = .center
        label.font = UIFont(name: "HelveticaNeue-Medium", size: 12)
        return label
    }()

    let homeTeamStatistic: UILabel = {
        let label = UILabel()
        label.text = String(12)
        label.textColor = ColorCodes.darkGray
        label.textAlignment = .right
        label.font = UIFont(name: "HelveticaNeue-CondensedBold", size: 20)
        return label
    }()

    let awayTeamStatistic: UILabel = {
        let label = UILabel()
        label.text = String(2)
        label.textColor = ColorCodes.darkGray
        label.textAlignment = .left
        label.font = UIFont(name: "HelveticaNeue-CondensedBold", size: 20)
        return label
    }()

    var homeTeamStatisticBar: UIView = {
        let view = UIView()
        view.backgroundColor = UIColor.lightGray
        return view
    }()

    var awayTeamStatisticBar: UIView = {
        let view = UIView()
        view.backgroundColor = UIColor.darkGray
        return view
    }()

    let barCenter = pitchWidth! / 2
    var barWidthHome: CGFloat = 10.0
    var barWidthAway: CGFloat = 10.0

    var statistic: Statistic? {
        didSet {
            guard let statisticName = statistic?.name else { return }
            guard let homeValue = statistic?.home else { return }
            guard let awayValue = statistic?.away else { return }
            guard let homeBar = statistic?.homeBar else { return }
            guard let awayBar = statistic?.awayBar else { return }

            statisticTitleLabel.text = statisticName
            homeTeamStatistic.text = homeValue.description
            awayTeamStatistic.text = awayValue.description
            barWidthHome = homeBar
            barWidthAway = awayBar

            print("Width HomeCell: \(barWidthHome)")
            print("Width AwayCell: \(barWidthAway)")

            homeTeamStatisticBar.anchor(top: topAnchor, left: nil, bottom: nil, right: rightAnchor, paddingTop: 4, paddingLeft: 0, paddingBottom: 0, paddingRight: barCenter, width: barWidthHome, height: 16)
            awayTeamStatisticBar.anchor(top: topAnchor, left: leftAnchor, bottom: nil, right: nil, paddingTop: 4, paddingLeft: barCenter, paddingBottom: 0, paddingRight: 0, width: barWidthAway, height: 16)

            self.homeTeamStatisticBar.layoutIfNeeded()
            self.awayTeamStatisticBar.layoutIfNeeded()
        }
    }

    override func setupCell() {
        super.setupCell()

        self.setNeedsLayout()

        backgroundColor = .white

        addSubview(statisticTitleLabel)
        addSubview(homeTeamStatistic)
        addSubview(awayTeamStatistic)
        addSubview(homeTeamStatisticBar)
        addSubview(awayTeamStatisticBar)


        statisticTitleLabel.anchor(top: nil, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 4, paddingRight: 0, width: 0, height: 0)
        addConstraint(NSLayoutConstraint(item: statisticTitleLabel, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0))
        homeTeamStatistic.anchor(top: topAnchor, left: leftAnchor, bottom: nil, right: nil, paddingTop: 0, paddingLeft: 20, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
        awayTeamStatistic.anchor(top: topAnchor, left: nil, bottom: nil, right: rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 20, width: 0, height: 0)
    }
}

extension UIView {

    func anchor(top: NSLayoutYAxisAnchor?, left: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, right: NSLayoutXAxisAnchor?, paddingTop: CGFloat, paddingLeft: CGFloat, paddingBottom: CGFloat, paddingRight: CGFloat, width: CGFloat, height: CGFloat) {

        self.translatesAutoresizingMaskIntoConstraints = false

        if let top = top {
            self.topAnchor.constraint(equalTo: top, constant: paddingTop).isActive = true
        }

        if let left = left {
            self.leftAnchor.constraint(equalTo: left, constant: paddingLeft).isActive = true
        }

        if let bottom = bottom {
            self.bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom).isActive = true
        }

        if let right = right {
            self.rightAnchor.constraint(equalTo: right, constant: -paddingRight).isActive = true
        }

        if width != 0 {
            self.widthAnchor.constraint(equalToConstant: width).isActive = true
        }

        if height != 0 {
            self.heightAnchor.constraint(equalToConstant: height).isActive = true
        }
    }
}

My Statistics CollectionView

import UIKit

class GameStatistics: NSObject, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {

    let blackView = UIView()

    let collectionView: UICollectionView = {
        let layout = UICollectionViewFlowLayout()
        let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
        cv.backgroundColor = UIColor.white
        return cv
    }()

    let cellId = "cellId"
    let sectionHeader = "sectionHeader"
    let sectionFooter = "sectionFooter"

    let cellHeight: CGFloat = 40
    let headerHeight: CGFloat = 80
    let footerHeight: CGFloat = 50
    let cellSpacing: CGFloat = 0

    var statistics = [Statistic(name: "Tore", home: 0, away: 0, homeBar: 1.0, awayBar: 1.0), //Goals
                      Statistic(name: "Schüsse aufs Tor", home: 0, away: 0, homeBar: 1.0, awayBar: 1.0), //Shots on Target
                      Statistic(name: "Schüsse neben das Tor", home: 0, away: 0, homeBar: 1.0, awayBar: 1.0), //Shots off Target
                      Statistic(name: "Freistöße", home: 0, away: 0, homeBar: 1.0, awayBar: 1.0), //Free Kicks
                      Statistic(name: "Eckbälle", home: 0, away: 0, homeBar: 1.0, awayBar: 1.0), //Corner Kicks
                      Statistic(name: "Fouls", home: 0, away: 0, homeBar: 1.0, awayBar: 1.0), //Fouls
                      Statistic(name: "Abseits / Mittellinie", home: 0, away: 0, homeBar: 1.0, awayBar: 1.0), //Offside / Centerline
                      Statistic(name: "Strafen", home: 0, away: 0, homeBar: 1.0, awayBar: 1.0)] //Cautions

    var barWidthHome: CGFloat = 1.0
    var barWidthAway: CGFloat = 1.0
    var statisticValueSum: Int = 1
    var valueHomeTeam: Int = 1
    var valueAwayTeam: Int = 1

    func updateGoals() {
        valueHomeTeam = UserDefaults.standard.integer(forKey: "homegoals")
        valueAwayTeam = UserDefaults.standard.integer(forKey: "awaygoals")
        statisticValueSum = valueHomeTeam + valueAwayTeam

        barWidthHome = CGFloat((Int(pitchWidth! / 2) - 40) * valueHomeTeam / statisticValueSum)
        barWidthAway = CGFloat((Int(pitchWidth! / 2) - 40) * valueAwayTeam / statisticValueSum)

        statistics[0] = Statistic(name: "Tore", home: valueHomeTeam, away: valueAwayTeam, homeBar: barWidthHome, awayBar: barWidthAway)
    }

    func showStatistics() {

        if let window = UIApplication.shared.keyWindow {

            blackView.backgroundColor = UIColor(white: 0, alpha: 0.5)

            blackView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleDismiss)))

            window.addSubview(blackView)
            window.addSubview(collectionView)

            // Dynamic Height of Collection View
            let value: CGFloat = CGFloat(statistics.count)
            let height: CGFloat = value * cellHeight + (value - 1) * cellSpacing + headerHeight + footerHeight
            let y = window.frame.height - height

            blackView.frame = window.frame
            collectionView.frame = CGRect(x: 0, y: window.frame.height, width: window.frame.width, height: height)

            blackView.alpha = 0

            UIView.animate(withDuration: 1, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {

                self.blackView.alpha = 1
                self.collectionView.frame = CGRect(x: 0, y: y, width: self.collectionView.frame.width, height: self.collectionView.frame.height)

            }, completion: nil)
        }
    }

    @objc func handleDismiss() {
        UIView.animate(withDuration: 0.5) {
            self.blackView.alpha = 0

            if let window = UIApplication.shared.keyWindow {
                self.collectionView.frame = CGRect(x: 0, y: window.frame.height, width: self.collectionView.frame.width, height: self.collectionView.frame.height)
            }
        }
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return statistics.count
    }


    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! GameStatisticCell

        cell.statistic = statistics[indexPath.item]

        cell.layoutIfNeeded()

        //dump(statistics)
        return cell

    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: collectionView.frame.width, height: cellHeight)
    }

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

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

    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {

        switch  kind {

            case UICollectionElementKindSectionHeader:
                let supplementaryView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: sectionHeader, for: indexPath)

                return supplementaryView
            case UICollectionElementKindSectionFooter:
                let supplementaryView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: sectionFooter, for: indexPath)

                return supplementaryView
            default:
                fatalError("Unexpected element kind")
        }
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {

        return CGSize(width: collectionView.frame.width, height: headerHeight)
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {

        return CGSize(width: collectionView.frame.width, height: footerHeight)
    }

    override init() {
        super.init()

        collectionView.dataSource = self
        collectionView.delegate = self

        collectionView.register(GameStatisticCell.self, forCellWithReuseIdentifier: cellId)
        collectionView.register(GameStatisticHeader.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: sectionHeader)
        collectionView.register(GameStatisticFooter.self, forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, withReuseIdentifier: sectionFooter)
    }


}

You should not add new constraints as those will obviously conflict with already existing ones (different padding). You should rather hold a reference to the dynamic constraints created in the setupCell and only update them in the statistic didSet {}

Solved it with two helper functions where I can set the constraints and update them in my statistic didSet. Took some time, but finally the above comment took me on the right track. Thank you.

func updateHomeBar() {
    homeWidth?.constant = barWidthHome
    homeTeamStatisticBar.setNeedsLayout()
}

func homeBarConstraints() {
    homeTeamStatisticBar.translatesAutoresizingMaskIntoConstraints = false

    var homeConstraints: [NSLayoutConstraint] = [
        homeTeamStatisticBar.topAnchor.constraint(equalTo: topAnchor, constant: 4),
        homeTeamStatisticBar.rightAnchor.constraint(equalTo: rightAnchor, constant: -barCenter),
        homeTeamStatisticBar.heightAnchor.constraint(equalToConstant: 16)]

    homeWidth = homeTeamStatisticBar.widthAnchor.constraint(equalToConstant: barWidthHome)
    homeConstraints.append(homeWidth!)

    NSLayoutConstraint.activate(homeConstraints)
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM