errors with UICollectionViewFlowLayout scrolling - ios

I am setting up a UiCollectionView and I am trying to have the view scroll horizontally. The View is setup up and I can see labels and images but it does not scroll at all. The height of the collectionViewCell itself is 350 and how i am setting up the cell is
let SubView: UIView = {
let view = UIView()
view.backgroundColor = GREEN_Theme
view.layer.cornerRadius = 10
return view
}()
let headerLabel: UILabel = {
let label = UILabel()
label.text = "label"
label.font = UIFont.systemFont(ofSize: 30)
label.font = .boldSystemFont(ofSize: 30)
label.numberOfLines = 0
label.textColor = UIColor.black
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
addSubview(SubView)
SubView.anchors(top: topAnchor, topPad: 0, bottom: nil, bottomPad: 0, left: leftAnchor, leftPad: 0, right: rightAnchor, rightPad: 0, height: 1, width: self.bounds.width - 20)
addSubview(headerLabel)
headerLabel.anchors(top: SubView.bottomAnchor, topPad: 5 , bottom: nil, bottomPad: 0, left: safeAreaLayoutGuide.leftAnchor, leftPad: 15, right: nil, rightPad: 0, height: 50, width: 200)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
let recentCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.backgroundColor = UIColor.clear
collectionView.translatesAutoresizingMaskIntoConstraints = false
return collectionView
}()
func setupViews() {
backgroundColor = UIColor.clear
addSubview(recentCollectionView)
contentView.isUserInteractionEnabled = true
recentCollectionView.dataSource = self
recentCollectionView.delegate = self
recentCollectionView.isUserInteractionEnabled = true
recentCollectionView.backgroundColor = UIColor.blue
recentCollectionView.register(recentCell.self, forCellWithReuseIdentifier: cellID)
recentCollectionView.layer.zPosition = 1
recentCollectionView.topAnchor.constraint(equalTo: topAnchor, constant: 40).isActive = true
recentCollectionView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 5).isActive = true
recentCollectionView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -5).isActive = true
recentCollectionView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -40).isActive = true
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return reviews.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath) as! recentCell
cell.configureCell(reviews[indexPath.row])
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 100, height: frame.height - 5)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 100, left: 14, bottom: 0, right: 14)
}
}
class recentCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
let imageView: UIImageView = {
let iv = UIImageView()
iv.image = UIImage(named: "rake")
iv.contentMode = .scaleAspectFill
iv.layer.cornerRadius = 16
iv.layer.masksToBounds = true
return iv
}()
let nameLabel: UILabel = {
let label = UILabel()
label.text = "Zach Wilcox"
label.font = UIFont.systemFont(ofSize: 14)
label.numberOfLines = 2
return label
}()
let categoryLabel: UILabel = {
let label = UILabel()
label.text = "Yard Work"
label.font = UIFont.systemFont(ofSize: 13)
label.textColor = UIColor.darkGray
return label
}()
let starControls = starStackView()
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
addSubview(imageView)
addSubview(nameLabel)
addSubview(categoryLabel)
addSubview(starControls)
starControls.translatesAutoresizingMaskIntoConstraints = false
starControls.distribution = .fillEqually
imageView.frame = CGRect(x:0, y: 0, width: frame.width, height: frame.width)
nameLabel.anchors(top: imageView.bottomAnchor, topPad: 1, bottom: nil, bottomPad: 0, left: imageView.leftAnchor, leftPad: 0, right: nil, rightPad: 0, height: 20, width: 100)
categoryLabel.anchors(top: nameLabel.bottomAnchor, topPad: 1, bottom: nil, bottomPad: 0, left: imageView.leftAnchor, leftPad: 0, right: nil, rightPad: 0, height: 20, width: 100)
starControls.anchors(top: categoryLabel.bottomAnchor, topPad: 1, bottom: nil, bottomPad: 0, left: imageView.leftAnchor, leftPad: 0, right: nil, rightPad: 0, height: 20, width: 100)
}
In my console, I see
the item height must be less than the height of the UICollectionView minus the section insets top and bottom values, minus the content insets top and bottom values.
I have tried to set the background colors to blue in the recentCollectionView and the collectionView itself to see if the if the height of the cell is exceeding the height of the collectionView but it is not.
images below,
the first image, is the size of the entire contentView.
and this image is the size of the recentCollectionView
as we can see with the images, the recentCollectionView is not larger than the collectionView. Why am I seeing this error and why will my collectionViewFlowLayout not scroll?

I have found an answer shortly after posting my question but did not want to delete just in case some other newbie had the same problem. All I had to do was add the recentCollectionView to the contentView's subview not simply just addSubview.
contentView.addSubview(recentCollectionView)
super simple fix.

Related

UICollectionView's background is a screenshot of the initial position of the scrollview?

This seems like a weird bug and I have adapted the code to see the bug better. By default the background color of my UICollectionView seems to be .systemBackground, but when I set it to .clear, instead of having a clear background, I have a "ghost" of the starting position of the first items in the scroll as a abckground. What could be happening?
The functions of the UICollectionView protocol:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 30
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "LineCell", for: indexPath) as! LineCell
cell.changeSize(indexPath.row)
cell.text.text = String(indexPath.row)
return cell
}
How I'm adding the view:
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
layout.estimatedItemSize = CGSize(width: 50, height: 80)
numberPicker = UICollectionView(frame: .zero, collectionViewLayout: layout)
secondsControlHolder.addSubview(numberPicker!)
numberPicker?.delegate = delegate
numberPicker?.dataSource = delegate
numberPicker?.register(LineCell.self, forCellWithReuseIdentifier: "LineCell")
numberPicker?.translatesAutoresizingMaskIntoConstraints = false
numberPicker?.heightAnchor.constraint(equalToConstant: 120).isActive = true
numberPicker?.bottomAnchor.constraint(equalTo: secondsControlHolder.bottomAnchor).isActive = true
numberPicker?.leadingAnchor.constraint(equalTo: secondsControlHolder.leadingAnchor).isActive = true
numberPicker?.trailingAnchor.constraint(equalTo: secondsControlHolder.trailingAnchor).isActive = true
numberPicker?.backgroundColor = .clear
numberPicker?.showsVerticalScrollIndicator = false
numberPicker?.showsHorizontalScrollIndicator = false
numberPicker?.isPagingEnabled = false
numberPicker?.contentInset = UIEdgeInsets(top: 0, left: (delegate.view.frame.size.width)/2, bottom: 0, right: (delegate.view.frame.size.width)/2)
let arrow = UIView()
let arrowTriangle = UIImageView(image: UIImage(systemName: "arrowtriangle.up.fill"))
secondsControlHolder.addSubview(arrow)
secondsControlHolder.addSubview(arrowTriangle)
arrow.translatesAutoresizingMaskIntoConstraints = false
arrow.bottomAnchor.constraint(equalTo: secondsControlHolder.bottomAnchor, constant: -10).isActive = true
arrow.centerXAnchor.constraint(equalTo: secondsControlHolder.centerXAnchor).isActive = true
arrow.heightAnchor.constraint(equalToConstant: 100).isActive = true
arrow.backgroundColor = .label
arrow.widthAnchor.constraint(equalToConstant: 5).isActive = true
arrow.isUserInteractionEnabled = false
arrowTriangle.translatesAutoresizingMaskIntoConstraints = false
arrowTriangle.centerXAnchor.constraint(equalTo: arrow.centerXAnchor).isActive = true
arrowTriangle.topAnchor.constraint(equalTo: secondsControlHolder.bottomAnchor, constant: -130).isActive = true
arrowTriangle.tintColor = .label
arrowTriangle.heightAnchor.constraint(equalToConstant: 23).isActive = true
arrowTriangle.widthAnchor.constraint(equalToConstant: 30).isActive = true
The LineCell class:
import UIKit
class LineCell: UICollectionViewCell {
let lineHolder = UIView()
let text = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
text.text = "test"
text.textColor = .white
lineHolder.translatesAutoresizingMaskIntoConstraints = false
lineHolder.addSubview(text)
text.translatesAutoresizingMaskIntoConstraints = false
text.topAnchor.constraint(equalTo: lineHolder.topAnchor).isActive = true
text.bottomAnchor.constraint(equalTo: lineHolder.bottomAnchor).isActive = true
text.leadingAnchor.constraint(equalTo: lineHolder.leadingAnchor).isActive = true
text.trailingAnchor.constraint(equalTo: lineHolder.trailingAnchor).isActive = true
addSubview(lineHolder)
// layer.backgroundColor = CGColor(red: 100, green: 100, blue: 100, alpha: 1)
// setupLineHolder()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupLineHolder(){
lineHolder.frame = CGRect(x: 0, y: 0, width: 1, height: 80)
lineHolder.backgroundColor = .label
// setupLine()
}
func changeSize(_ index: Int){
if index % 5 == 0 {
lineHolder.frame = CGRect(x: 0, y: 0, width: 50, height: 80)
lineHolder.backgroundColor = .label
}else{
lineHolder.frame = CGRect(x: 0, y: 15, width: 50, height: 50)
lineHolder.backgroundColor = .label.withAlphaComponent(0.7)
}
}
}
Turns out I was calling setupControlsArea() twice. It wasn't just the UICollectionView that was duplicated, but the whole controls area view.

Vertical scrolling with TableView inside horizontal CollectionView

I have a horizontal UICollectionView (with paging) where each cell has a fullscreen UITableView. The parent ViewController has a navigation bar with large titles where an animation occurs during vertical scrolling. However, since my table views are within the horizontal collection view, the large navigation bar does not animate when scrolling.
I have attempted a solution of setting a delegate for the tableview scrolling and then set the y offset of the collection view but the result glitches and does not work correctly.
Here is my current code (without TableView/CollectionView delegate/datasource methods)
class HomeViewController: UIViewController, HomeCellDelegate {
var delegate: HomeViewControllerDelegate!
var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
create()
}
func create() {
view.backgroundColor = .background
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
layout.sectionInset = .zero
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
view.addSubview(collectionView)
collectionView.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
collectionView.backgroundColor = .clear
collectionView.showsHorizontalScrollIndicator = false
collectionView.isPagingEnabled = true
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(HomeCell.self, forCellWithReuseIdentifier: "cellID")
}
//Delegate from UICollectionView cell (the table view)
func tableViewDidScroll(_ contentOffset: CGPoint) {
collectionView.contentOffset.y = contentOffset.y //Glitches
}
}
protocol HomeCellDelegate {
func tableViewDidScroll(_ contentOffset: CGPoint)
}
class HomeCell: UICollectionViewCell {
var events = [String]()
let tableView = UITableView()
var delegate: HomeCellDelegate!
override init(frame: CGRect) {
super.init(frame: frame)
create()
}
func create() {
backgroundColor = .clear
addSubview(tableView)
tableView.anchor(top: topAnchor, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
tableView.contentInsetAdjustmentBehavior = .never
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = CGPoint(x: 0.0, y: scrollView.contentOffset.y + scrollView.adjustedContentInset.top)
delegate.tableViewDidScroll(offset)
}
required init?(coder: NSCoder) {
fatalError()
}
}

UICollectionView x3 Within each other creating a collection view slider within a header collection view

I was wondering if anyone can show/help me.
As you can see in image 2 that I am trying to create a card slider, within my UICOLLECTIONVIEW at the top.
I am aware that to create a UICollectionView slider you need to first create the collectionview to hold the slider then another within it. So all together I have 3 UICOLLECTIONVIEWS within each other.
I have been following tutorials to get this far but I can’t figure this one out. Image one is where I am currently.
But currently, all I seem to be doing is overlaying the current UICollection not making it go within it, as you can see in image 1.
[
import UIKit
class MyFeedsHeader: UICollectionViewCell, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
private let cellId = "appCellId"
let profileImageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFill
iv.clipsToBounds = true
iv.backgroundColor = .lightGray
return iv
}()
let feedsTitle: UILabel = {
let title = UILabel()
title.text = "Feeds"
title.font = UIFont.boldSystemFont(ofSize: 30)
return title
}()
let followingLabel: UILabel = {
let title = UILabel()
title.text = "00 Following"
title.font = UIFont.boldSystemFont(ofSize: 16)
title.textColor = .lightGray
return title
}()
// --------------------
let appsCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
// layout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.backgroundColor = UIColor.clear
collectionView.translatesAutoresizingMaskIntoConstraints = false
return collectionView
}()
//--------------------
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(profileImageView)
profileImageView.anchor(top: self.topAnchor, left: nil, bottom: nil, right: self.rightAnchor, paddingTop: 16, paddingLeft: 0, paddingBottom: 0, paddingRight: 20, width: 50, height: 50)
profileImageView.layer.cornerRadius = 5 / 1
addSubview(feedsTitle)
feedsTitle.anchor(top: self.topAnchor, left: self.leftAnchor, bottom: nil, right: nil, paddingTop: 20, paddingLeft: 20, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
addSubview(followingLabel)
followingLabel.anchor(top: feedsTitle.bottomAnchor, left: self.leftAnchor, bottom: nil, right: nil, paddingTop: 20, paddingLeft: 20, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
setupViews()
self.backgroundColor = .white
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// ---------------------------------- remove below if can't figure out
func setupViews() {
// --------
appsCollectionView.dataSource = self
appsCollectionView.delegate = self
appsCollectionView.register(AppCell.self, forCellWithReuseIdentifier: cellId)
addSubview(appsCollectionView)
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-8-[v0]-8-|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": appsCollectionView]))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": appsCollectionView]))
//--------
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
return collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath)
}
class AppCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
backgroundColor = UIColor.red
}
}
}
I recommend starting with the outermost UICollectionView first, and as you get each one working you can move on to the next.
To get more detail on exactly what is going on with the app currently you can use the View Debugger to see if everything is nested correctly and any constraints are doing what they should.
To get the effect you are looking for your first stop should be implementing the methods coming from UICollectionViewDelegateFlowLayoutlike sizeForItemAt, insetForSectionAt, & minimumLineSpacingForSectionAt. These will define spacing and layout for the cells to interact with.
These delegate methods are all part of the protocol UICollectionViewDelegateFlowLayout which means that your collection view (who's delegate you set to self AKA MyFeedsHeader) will ask your class MyFeedsHeader for information about how to layout the cells if MyFeedsHeader has the functions on it. The is the same thing you did with the func collectionView:numberOfItemsInSection.
Hope that points you in the right direction! Best of luck!

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 e.g. 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 (e.g. 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)
}

Inaccuracy addSubview in UICollectionViewCell

There was a big problem that I can not solve for a couple of days.
I have a UIViewController with UICollectionView in which there will be 3 cells with different content. Here is my class:
class PostView: AppViewAddMenu, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
var homeController: FeedController?
var contentArray = [PostContentModel]()
var postInfo: PostModel?
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 3
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.delegate = self
cv.dataSource = self
cv.backgroundColor = .white
return cv
}()
override func setupViewController() {
collectionView.register(PostViewCell.self, forCellWithReuseIdentifier: "cellId")
view.addSubview(collectionView)
view.addConstraintsWithFormat("H:|[v0]|", views: collectionView)
view.addConstraintsWithFormat("V:|-60-[v0]|", views: collectionView)
fetchPost(id: postInfo?.id)
}
func fetchPost(id: Int?){
guard let ID = id else {
print("ID faild")
return
}
let url: String = "https://FTP.ru/wp-json/mobileApi/v1/post/\(ID)"
ApiService.sharedInstance.fetchContent(url: url, completion: { (Posts: [PostContentModel]) in
self.contentArray = Posts
self.collectionView.reloadData()
})
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! PostViewCell
cell.contentArray = contentArray
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: 1000)
}
}
The idea of the first cell is to take json from a site that looks like this: [switch, content]. From the switch value in UICollectionViewCell, different functions are called, which add an element in UICollectionViewCell via addSubview (). Here's the code:
class PostViewCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
anchor = self.topAnchor
}
var anchor: NSLayoutYAxisAnchor?
var contentArray: [PostContentModel]?{
didSet{
for number in contentArray! {
if(number.switcher == 1) {
anchor = addTextContent(content: number.content!, bottomAnchor: anchor!)
}
if(number.switcher == 2){
anchor = addImageContent(content: number.content!, bottomAnchor: anchor!)
}
if(number.switcher == 3){
anchor = addCaptionFirst(content: number.content!, bottomAnchor: anchor!)
}
if(number.switcher == 4){
anchor = addButtonLink(content: number.content!, link: number.link!, bottomAnchor: anchor!)
}
}
}
}
func addTextContent(content: String, bottomAnchor: NSLayoutYAxisAnchor) -> NSLayoutYAxisAnchor{
let shortContentPost: UILabel = {
let label = UILabel()
label.text = content
label.numberOfLines = 0
label.font = UIFont.systemFont(ofSize: 16, weight: UIFont.Weight.regular)
return label
}()
addSubview(shortContentPost)
shortContentPost.anchor(bottomAnchor, left: self.leftAnchor, bottom: nil, right: self.rightAnchor, topConstant: 15, leftConstant: 15, bottomConstant: 0, rightConstant: 15, widthConstant: 0, heightConstant: 0)
return shortContentPost.bottomAnchor
}
func addImageContent(content: String, bottomAnchor: NSLayoutYAxisAnchor) -> NSLayoutYAxisAnchor {
let image: CustomImageView = {
let image = CustomImageView()
image.image = UIImage(named: content)
image.contentMode = .scaleAspectFill
image.clipsToBounds = true
return image
}()
let imageURL = "https://brodude.ru/wp-content/uploads/" + content.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
image.loadImageUsingUrlString(urlString: imageURL)
addSubview(image)
image.anchor(bottomAnchor, left: self.leftAnchor, bottom: nil, right: self.rightAnchor, topConstant: 15, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 250)
return image.bottomAnchor
}
func addCaptionFirst(content: String, bottomAnchor: NSLayoutYAxisAnchor) -> NSLayoutYAxisAnchor {
let Caption: UILabel = {
let lb = UILabel()
lb.text = content
lb.numberOfLines = 0
lb.font = UIFont.systemFont(ofSize: 20, weight: UIFont.Weight.semibold)
return lb
}()
addSubview(Caption)
Caption.anchor(bottomAnchor, left: self.leftAnchor, bottom: nil, right: self.rightAnchor, topConstant: 30, leftConstant: 15, bottomConstant: 0, rightConstant: 15, widthConstant: 0, heightConstant: 0)
return Caption.bottomAnchor
}
func addButtonLink(content: String, link: String, bottomAnchor: NSLayoutYAxisAnchor) -> NSLayoutYAxisAnchor {
let button: LinkButton = {
let bt = LinkButton()
bt.LinkString = link
bt.titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: UIFont.Weight.semibold)
bt.setTitle(content, for: UIControlState.normal)
bt.sizeToFit()
bt.titleLabel?.numberOfLines = 0
bt.contentHorizontalAlignment = .left
bt.setTitleColor(UIColor(red: 0.07, green: 0.32, blue: 0.89, alpha: 1.0), for: UIControlState.normal)
bt.addTarget(self, action: #selector(linkOut), for: .touchUpInside)
return bt
}()
addSubview(button)
button.anchor(bottomAnchor, left: self.leftAnchor, bottom: nil, right: self.rightAnchor, topConstant: 20, leftConstant: 15, bottomConstant: 0, rightConstant: 15, widthConstant: 0, heightConstant: 0)
return button.bottomAnchor
}
The algorithm works satisfactorily, but the problem begins when the cell is updated. When scrolling, with collectionView.reloadData (). New subview layered on the past, the text becomes fatter and clogged device memory. The process continues indefinitely until it gives an error.
Example of a problem:, ,
Sorry for my English.
First of all, instead of:
addSubview(shortContentPost)
use:
contentView.addSubview(shortContentPost)
Do this for all the subviews that you add. contentView is supposed to hold your content.
Implement this prepareForReuse in your cell:
override func prepareForReuse() {
super.prepareForReuse()
let subviews = contentView.subviews
for subview in subviews {
subview.removeFromSuperview()
}
}
This will clean your cell's content before reusing the cell.
Although I would STRONGLY recommend NOT to add/remove those subviews dynamically, because then you lose much of the reuse mechanism. I would add all the views (labels/imageViews) to the cell by default, and then use their isHidden property to hide/unhide them based on the content (e.g., if an image should be show, set the isHidden to true on all the others). This would be performance-wise better for reusing, because then the UI objects will not have to be recreated everytime a cell is reused, you would just reconfigure the content of the labels/imageViews.

Resources