CollectionView Cells are not visible - swift

Using Swift 5.1.3, iOS13.3,
I am trying to display a horizontal CollectionViewController as a ChildView Controller.
The issue: All Cells are invisible !
The print-statement in the Code (shown with lots of !!!!!!!! in the comment below)
Test.CardHeaderCell: 0x7fa563709370; baseClass = UICollectionViewCell; frame = (1059 23; 343 279); hidden = YES; layer = <CALayer: 0x6000024de080>>
Here is the code:
override func viewDidLoad() {
// ...
let cardsHorizontalController = CardsHorizontalController()
self.addChild(cardsHorizontalController)
self.view.addSubview(cardsHorizontalController.view)
self.didMove(toParent: cardsHorizontalController)
cardsHorizontalController.view.translatesAutoresizingMaskIntoConstraints = false
cardsHorizontalController.view.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 70.0).isActive = true
cardsHorizontalController.view.heightAnchor.constraint(equalToConstant: 390.0).isActive = true
cardsHorizontalController.view.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
}
Here is the HorizontalController
class CardsHorizontalController: HorizontalSnappingController, UICollectionViewDelegateFlowLayout {
let cellId = "horizontalCardID"
override func viewDidLoad() {
super.viewDidLoad()
collectionView.backgroundColor = .green
collectionView.register(CardHeaderCell.self, forCellWithReuseIdentifier: cellId)
if let layout = collectionViewLayout as? UICollectionViewFlowLayout {
layout.scrollDirection = .horizontal
}
collectionView.showsHorizontalScrollIndicator = true
collectionView.showsVerticalScrollIndicator = false
// do the insets here instead of the optional "insetForSectionAt" delegate-method
// i.e. this helps to get the scrolled cells aligned in the middle of the screen once 1 cell wide scrolled...
collectionView.contentInset = .init(top: 65.0, left: 16.0, bottom: 0.0, right: 16.0)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return .init(width: view.frame.width - 32.0, height: 279.0)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 17
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath)
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
print(cell)
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
return cell
}
}
And the Cell:
class CardHeaderCell: UICollectionViewCell {
let imageView = UIImageView(cornerRadius: 10.0)
override init(frame: CGRect) {
super.init(frame: frame)
// blur view
imageView.backgroundColor = UIColor(red: 0.086, green: 0.086, blue: 0.086, alpha: 0.35)
let visualEffectView = UIVisualEffectView(frame: imageView.frame)
visualEffectView.effect = UIView.customBlurEffect()
visualEffectView.autoresizingMask = UIView.AutoresizingMask(rawValue: UIView.AutoresizingMask.flexibleWidth.rawValue | UIView.AutoresizingMask.flexibleHeight.rawValue)
imageView.addSubview(visualEffectView)
let stackView = VerticalStackView(arrangedSubviews: [
imageView
], spacing: 12.0)
addSubview(stackView)
stackView.fillSuperview(padding: .init(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0))
}
required init?(coder aDecoder: NSCoder) {
fatalError()
}
}
And for completeness reasons, here the rest of the custom classes...
class HorizontalSnappingController: UICollectionViewController {
init() {
let layout = BetterSnappingLayout()
layout.scrollDirection = .horizontal
super.init(collectionViewLayout: layout)
collectionView.decelerationRate = .fast
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class BetterSnappingLayout: UICollectionViewFlowLayout {
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
guard let collectionView = collectionView else {
return super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity)
}
let nextX: CGFloat
if proposedContentOffset.x <= 0 || collectionView.contentOffset == proposedContentOffset {
nextX = proposedContentOffset.x
} else {
nextX = collectionView.contentOffset.x + (velocity.x > 0 ? collectionView.bounds.size.width : -collectionView.bounds.size.width)
}
let targetRect = CGRect(x: nextX, y: 0, width: collectionView.bounds.size.width, height: collectionView.bounds.size.height)
var offsetAdjustment = CGFloat.greatestFiniteMagnitude
let horizontalOffset = proposedContentOffset.x + collectionView.contentInset.left
let layoutAttributesArray = super.layoutAttributesForElements(in: targetRect)
layoutAttributesArray?.forEach({ (layoutAttributes) in
let itemOffset = layoutAttributes.frame.origin.x
if fabsf(Float(itemOffset - horizontalOffset)) < fabsf(Float(offsetAdjustment)) {
offsetAdjustment = itemOffset - horizontalOffset
}
})
return CGPoint(x: proposedContentOffset.x + offsetAdjustment, y: proposedContentOffset.y)
}
}
class VerticalStackView: UIStackView {
init(arrangedSubviews: [UIView], spacing: CGFloat = 0, alignment: UIStackView.Alignment = .center) {
super.init(frame: .zero)
arrangedSubviews.forEach({addArrangedSubview($0)})
self.spacing = spacing
self.alignment = alignment
self.axis = .vertical
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

I finally found a solution:
The class VerticalStackView had a default Alignment property .center.
When I replace .center by .fill as a default value - then everything works as expected !
The usage of the VerticalStackView remains the same (i.e. default value for alignment applies):
let stackView = VerticalStackView(arrangedSubviews: [
imageView
], spacing: 12.0)

Related

Why does this logic work well (UICollectionViewCell + CustomPaddingLabel)

When I make a message UI, need a dynamic height UICollectionViewCell which be calculated with label's height. But first try the cell when reuse, customLabel's layout did not change so the cell is maintained cell's layout which previously used.
I found that the problem is CustomPaddingLabel and make it working well. But I still can't understand why this logic make working well. Read layout life cylcle, UICollectionView life cycle, but that can't solve this question.
plz, explain why this logic can make working well, also why CustomPaddingLabel cause this problem. (When I try with UILabel, this problem does not occured)
This is UICollectionViewCell...
final class MessageCell: UICollectionViewCell {
static let identifier: String = .init(describing: MessageCell.self)
private let messageLabel: PaddingLabel = .init().then {
$0.numberOfLines = 0
$0.textColor = .black
$0.font = .systemFont(ofSize: 14.5)
$0.lineBreakStrategy = .pushOut
$0.layer.cornerRadius = 14
$0.clipsToBounds = true
$0.setContentHuggingPriority(.defaultLow, for: .horizontal)
$0.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
}
private let dateLabel = UILabel().then {
$0.font = .systemFont(ofSize: 10, weight: .regular)
$0.textColor = .secondaryLabel
$0.numberOfLines = 1
}
private var chatType: ChatType = .none
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubviews(messageLabel, dateLabel)
}
#available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("Does not use this initializer")
}
override func layoutSubviews() {
super.layoutSubviews()
}
override func prepareForReuse() {
super.prepareForReuse()
messageLabel.text = nil
dateLabel.text = nil
chatType = .none
self.messageLabel.snp.removeConstraints()
self.dateLabel.snp.removeConstraints()
self.messageLabel.layoutIfNeeded()
}
func setCell(with message: String, type: ChatType, dateString: String) {
self.messageLabel.text = message
self.chatType = type
self.dateLabel.text = dateString
self.configureLayouts()
self.configureProperties()
}
}
private extension MessageCell {
func configureLayouts() {
switch chatType {
case .none:
break
case .send:
messageLabel.snp.remakeConstraints { make in
make.top.bottom.equalToSuperview()
make.trailing.equalToSuperview().inset(20)
make.leading.greaterThanOrEqualToSuperview()
}
dateLabel.snp.remakeConstraints { make in
make.trailing.equalTo(messageLabel.snp.leading).offset(-4)
make.leading.greaterThanOrEqualToSuperview().inset(56)
make.bottom.equalTo(messageLabel)
}
case .receive:
messageLabel.snp.remakeConstraints { make in
make.top.bottom.equalToSuperview()
make.leading.equalToSuperview().inset(56)
make.trailing.lessThanOrEqualToSuperview()
}
dateLabel.snp.remakeConstraints { make in
make.leading.equalTo(messageLabel.snp.trailing).offset(4)
make.trailing.lessThanOrEqualToSuperview().inset(24)
make.bottom.equalTo(messageLabel)
}
}
}
func configureProperties() {
switch chatType {
case .none:
break
case .send:
messageLabel.backgroundColor = UIColor(red: 250/255, green: 230/255, blue: 76/255, alpha: 1)
case .receive:
messageLabel.backgroundColor = .white
}
}
}
and this is CustomPaddingLabel...
class PaddingLabel: UILabel {
private var topInset: CGFloat
private var bottomInset: CGFloat
private var leftInset: CGFloat
private var rightInset: CGFloat
override var intrinsicContentSize: CGSize {
let size = super.intrinsicContentSize
return CGSize(width: size.width + leftInset + rightInset, height: size.height + topInset + bottomInset)
}
override var bounds: CGRect {
didSet {
preferredMaxLayoutWidth = bounds.width - (leftInset + rightInset)
}
}
init(topInset: CGFloat = 8, bottomInset: CGFloat = 8, leftInset: CGFloat = 10, rightInset: CGFloat = 10) {
self.topInset = topInset
self.bottomInset = bottomInset
self.leftInset = leftInset
self.rightInset = rightInset
super.init(frame: .zero)
}
#available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("Does not use this initializer")
}
override func drawText(in rect: CGRect) {
let insets = UIEdgeInsets(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset)
super.drawText(in: rect.inset(by: insets))
}
}
Configure the cell height of the UICollectionVIew to dynamic according to the CustomPaddingLabel's height. But when the cell be reused, CustomPaddingLabel's layout is concreted so the next cell has unbalance fit layout.
I found the problem is from CustomPaddingLabel (use UILabel this problem doesn't happen), and fix it worked well. But I don't know why this problem was occured and why this logic solved that problem.
plz, explain this question.

CollectionView Cell Changing Size when screen reloads

I am creating a screen where the user can search for films and the results load in a collection View, everything loads perfectly but in the simulator when I clicked "command, shift, A" to change to light mode to make sure the colours would adapt correctly the Cells randomly changed size to full screen instead of what I have set.
This also happens when I leave the application for the home page and then click back into the app. I am creating everything programmatically so would need answer this way please.
Below is the code from my custom cell:
import UIKit
class FavouritesCell: UICollectionViewCell {
static let identifier = "FavouritesCell"
let movieTitle: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.lineBreakMode = .byWordWrapping
label.textColor = .secondaryLabel
//label.text = "Title"
label.adjustsFontSizeToFitWidth = true
label.textAlignment = .center
label.font = label.font.withSize(12)
label.minimumScaleFactor = 0.75
return label
}()
let image : UIImageView = {
let image = UIImageView()
image.clipsToBounds = true
image.translatesAutoresizingMaskIntoConstraints = false
//image.backgroundColor = .yellow
image.layer.cornerRadius = 10.0
return image
}()
let cancelItem : UILabel = {
let label = UILabel()
label.layer.borderWidth = 2.0
label.layer.borderColor = UIColor.systemGray.cgColor
label.text = "X"
label.textAlignment = .center
label.layer.cornerRadius = 15
//label.isHidden = true
return label
}()
override init(frame: CGRect) {
super.init(frame: .zero)
contentView.addSubview(image)
contentView.addSubview(movieTitle)
contentView.addSubview(cancelItem)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
image.frame = CGRect(x: 0, y: 25, width: contentView.width, height: contentView.height - 30 )
movieTitle.frame = CGRect(x: 0, y: 0, width: contentView.width, height: 25)
cancelItem.frame = CGRect(x: image.right - 25, y: image.top , width: 25, height: 25)
}
}
Here is the code from the view controller in relation to the collectionView:
private let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.minimumLineSpacing = 2
layout.minimumInteritemSpacing = 2
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.register(FavouritesCell.self, forCellWithReuseIdentifier: FavouritesCell.identifier)
collectionView.clipsToBounds = true
collectionView.backgroundColor = UIColor.systemBackground
return collectionView
}()
NSLayoutConstraint.activate([
searchText.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
searchText.heightAnchor.constraint(equalToConstant: 50),
searchText.leadingAnchor.constraint(equalTo: view.leadingAnchor),
searchText.trailingAnchor.constraint(equalTo: view.trailingAnchor),
collectionView.topAnchor.constraint(equalTo: searchText.bottomAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: (view.width / 3) - 4, height: (view.width / 2) - 2 )
}
image of CollectionView working normally
After the screen has been reloaded
Try to give your imageView a fixed height and fixed width and try it again, It will work

Multiple overlayed images in the Navigation bar title

I know how to center a single image in UINavigationBar but no idea how to do that with dynamic number of images. I have a chat app that supports group chats. The number of people in a group chat could be as little as 3 but there's no upper limit.
In the UINavigationBar, I have to set the title to show at least 4 or 5 overlayed images (but not more than that since it looks odd in the UINavigationBar) and a UILabel showing how many more users are in the same group chat (ie + 15 more). The title (all the images and the label) should be centered in the UINavigationBar. The images are being downloaded from the server.
When the user taps on the title (any of the images or the label in the UINavigationBar) it should trigger an action to show a full list of the users in a separate UIViewController
The number of the overlayed images is dynamic (based on each group chat) but I can't figure out how to do this. Here's what the image of what the end result should look like:
Has anyone done this before or have an idea how to accomplish this? Help is very much appreciated
UPDATE:
I've tried to accomplish this with UIStackView but I have multiple problems. Here's the code:
var navStackView : UIStackView = {
let stack = UIStackView()
stack.axis = .horizontal
stack.backgroundColor = .red
stack.alignment = .fill
stack.distribution = .fillEqually
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}()
var images = ["1", "2", "3", "4"]
override func viewDidLoad() {
super.viewDidLoad()
let navController = navigationController!
navController.navigationBar.addSubview(navStackView)
// x, y, w, h
navStackView.leadingAnchor.constraint(equalTo: navController.navigationBar.leadingAnchor).isActive = true
navStackView.trailingAnchor.constraint(equalTo: navController.navigationBar.trailingAnchor).isActive = true
navStackView.topAnchor.constraint(equalTo: navController.navigationBar.topAnchor).isActive = true
navStackView.bottomAnchor.constraint(equalTo: navController.navigationBar.bottomAnchor).isActive = true
for image in images {
let imageView = UIImageView()
imageView.image = UIImage(named: image)
imageView.layer.cornerRadius = imageView.bounds.height / 2
imageView.clipsToBounds = true
imageView.layer.masksToBounds = true
imageView.contentMode = .scaleAspectFill
// imageView.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
navStackView.addArrangedSubview(imageView)
navStackView.layoutIfNeeded()
}
navigationItem.titleView = navStackView
}
Here's the result so far (I'm stuck though, not sure how to accomplish it):
I'm not sure about stackView. But for a simple implementation I've used collectionView. Check the below strategy. You should be able to modify accordingly per your requirement.
import UIKit
class OverlayCell: UICollectionViewCell {
func didplay(with number: String) {
let view = UIView(frame: CGRect(x: 0, y: 0, width: 40.0, height: 40.0))
view.backgroundColor = UIColor.blue
view.layer.cornerRadius = 20.0
view.layer.masksToBounds = true
view.layer.borderColor = UIColor.white.cgColor
view.layer.borderWidth = 2.0
let label = UILabel(frame: CGRect(x: 2, y: 2, width: view.bounds.width - 4, height: view.bounds.height - 4))
label.textColor = .white
label.text = number
label.textAlignment = .center
view.addSubview(label)
contentView.addSubview(view)
contentView.transform = CGAffineTransform(scaleX: -1, y: 1)
}
}
class OverlayedView: UIView {
var mainView: UIView!
var imageCollection: UICollectionView!
//Static for now
let cellWidth: CGFloat = 40.0
let cellHeight: CGFloat = 40.0
var collectionWidth: CGFloat = 115.0
override init(frame: CGRect) {
super.init(frame: frame)
loadNib()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
loadNib()
}
private func loadNib() {
if let view = Bundle.main.loadNibNamed("OverlayedView", owner: self, options: nil)?.first as? UIView {
mainView = view
mainView.frame = self.bounds
self.backgroundColor = .black
addSubview(view)
}
}
var dataSource = ["4","3","2","1"]
func loadData() {
//dynamically calculate collectionWidth to be able to kepp it in center
collectionWidth = dataSource.count >= 4 ? CGFloat(dataSource.count) * cellWidth - CGFloat((dataSource.count - 1) * 15) : CGFloat(dataSource.count) * cellWidth - CGFloat((dataSource.count - 1) * 15) //CGFloat(dataSource.count * 15) here is the item spacing from delegate -15 inward so that we can get overlapping effect
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
imageCollection = UICollectionView(frame: CGRect(x: 0, y: 0, width: collectionWidth, height: self.bounds.height), collectionViewLayout: layout)
imageCollection.center = mainView.center
imageCollection.register(OverlayCell.self, forCellWithReuseIdentifier: "Cell")
//flip the collectionView so that it loads from right to left for overlapping effect
imageCollection.transform = CGAffineTransform(scaleX: -1, y: 1)
imageCollection.delegate = self
imageCollection.dataSource = self
mainView.addSubview(imageCollection)
}
}
extension OverlayedView: UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if dataSource.count > 4 {
return 4
}
return dataSource.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! OverlayCell
cell.didplay(with: dataSource[indexPath.row])
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let size = CGSize(width: 40.0 , height: 40.0)
return size
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0.0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return -15.0
}
}
Usage:
let navOverlay = OverlayedView(frame: CGRect(x: 0, y: 0, width: 250.0, height: 44.0))
navOverlay.loadData() . //pass your data to this method
navigationItem.titleView = navOverlay
I've figured it out finally. Not sure if this is the right way to accomplish it, but it's a way to accomplish it and it works great. The thing to notice - I have to calculate the navStackView width based on the number of images we have. More than 5-6 images gets too crouded, so, no more than 5 images.
The navStackView.spacing also is calculated based the width and the space you'd like between the images.
var navStackView : UIStackView = {
let stack = UIStackView()
stack.axis = .horizontal
stack.alignment = .fill
stack.distribution = .fillEqually
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}()
var moreLabel: UILabel = {
let label = UILabel()
label.text = "+ 5 more"
label.textColor = .black
label.textAlignment = .left
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
var images = ["1", "2", "3", "4", "3", "3"]
override func viewDidLoad() {
super.viewDidLoad()
let navController = navigationController!
navController.navigationBar.addSubview(navStackView)
// x, y, w, h
navStackView.widthAnchor.constraint(equalToConstant: 95).isActive = true
navStackView.centerYAnchor.constraint(equalTo: navController.navigationBar.centerYAnchor).isActive = true
navStackView.heightAnchor.constraint(equalToConstant: 35).isActive = true
navStackView.centerXAnchor.constraint(equalTo: navController.navigationBar.centerXAnchor).isActive = true
// image height = 35, image width = 35
// when subtracting spacing from NavStackView, we need to subtrack from the width as well for (items - 1)
switch images.count {
case 0:
print("0 images")
case 1:
changeNavStackWidth(constant: 60, spacing: 0)
moreLabel.isHidden = true
case 2:
changeNavStackWidth(constant: 80, spacing: 10)
moreLabel.isHidden = true
case 3:
changeNavStackWidth(constant: 95, spacing: -5)
moreLabel.isHidden = true
case 4:
changeNavStackWidth(constant: 110, spacing: -10)
moreLabel.isHidden = true
case 5:
changeNavStackWidth(constant: 95, spacing: -20)
moreLabel.isHidden = true
case 6...1000:
changeNavStackWidth(constant: 95, spacing: -20)
moreLabel.isHidden = false
default:
print("default")
}
for image in images {
let imageView = UIImageView()
imageView.image = UIImage(named: image)
imageView.layer.borderColor = UIColor.white.cgColor
imageView.layer.borderWidth = 1
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
navStackView.addArrangedSubview(imageView)
navStackView.layoutIfNeeded()
}
navController.navigationBar.addSubview(moreLabel)
// x, y ,w, h
moreLabel.leadingAnchor.constraint(equalTo: navStackView.trailingAnchor, constant: 50).isActive = true
moreLabel.topAnchor.constraint(equalTo: navStackView.topAnchor).isActive = true
moreLabel.bottomAnchor.constraint(equalTo: navStackView.bottomAnchor).isActive = true
navigationItem.titleView = navStackView
let stackTap = UITapGestureRecognizer()
stackTap.addTarget(self, action: #selector(stackTapped))
navStackView.isUserInteractionEnabled = true
navStackView.addGestureRecognizer(stackTap)
}
#objc func stackTapped() {
print("tapp")
}
func changeNavStackWidth(constant: CGFloat, spacing: CGFloat) {
navStackView.constraints.forEach { constraint in
if constraint.firstAttribute == .width {
constraint.constant = constant
}
}
navStackView.spacing = spacing
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
navStackView.subviews.forEach { $0.layer.cornerRadius = $0.frame.height / 2 }
}

Swift 4 - Multiple inheritance from classes 'NSObject' and 'UICollectionViewFlowLayout'

I am trying to programatically size the collection cell to be the width of the frame, however, the cell size doesn't change when I run the app. Is this the right function to call for Swift 4 and Xcode 9?
import UIKit
class SettingsLauncher: NSObject, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewFlowLayout {
let blackView = UIView()
let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = UIColor.white
return cv
}()
let cellID = "cell"
#objc func showMenu() {
// Show menu
if let window = UIApplication.shared.keyWindow { // Get the size of the entire window
blackView.backgroundColor = UIColor(white: 0, alpha: 0.5)
let height: CGFloat = 200
let y = window.frame.height - height // The y value to appear at the bottom of the screen
collectionView.frame = CGRect(x: 0, y: window.frame.height, width: window.frame.width, height: height)
// Add gesture recognizer on black view to dismiss menu
blackView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleDismiss)))
window.addSubview(blackView)
window.addSubview(collectionView)
blackView.frame = window.frame
blackView.alpha = 0
// Slow the animation down towards the end (curveEasOut)
UIView.animate(withDuration: 1, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
// Animate black view
self.blackView.alpha = 1
// Animate collection view
self.collectionView.frame = CGRect(x: 0, y: y, width: self.collectionView.frame.width, height: height)
}, completion: nil)
}
}
#objc func handleDismiss() {
// Dimisses menu view
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 6
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: 50)
}
override init() {
super.init()
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(MenuCell.self, forCellWithReuseIdentifier: cellID)
}
}
Edit:
This table view is being animated from the bottom of the page and presents a collection view, which is being called from the view controller that is using the pop up menu.
I now get the error:
Multiple inheritance from classes 'NSObject' and 'UICollectionViewFlowLayout'
UICollectionViewFlowLayout is not a protocol, it's a class. You need to use UICollectionViewDelegateFlowLayout instead of UICollectionViewFlowLayout.
Change
class SettingsLauncher: NSObject, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewFlowLayout
to
class SettingsLauncher: NSObject, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout

TableView rows dissapear when tapped

I have a table view embedded in an iCarousel item and firstly I can't seem to set the view frame whether it's in viewDidLayoutSubviews() but also when I tap the Tableview all the cells disappear. I have created the Tableview in its own ViewController as each carousel item will need editing buttons for the Tableviews they contain. If I don't put the Tableview in its own view controller the cells remain but I get an index out of range when scrolling a lot of views and I can't add editing buttons. I've run out of ideas and would love some pointers or tips.
class BillSplitterTableView: UIViewController, UITableViewDataSource, UITableViewDelegate {
var splitter: BillSplitter?
var tableView: UITableView!
var viewFrame: CGRect?
init(frame: CGRect, splitter: BillSplitter) {
super.init(nibName: nil, bundle: nil)
self.splitter = splitter
self.viewFrame = frame
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:)")
}
override func viewDidLoad() {
super.viewDidLoad()
let subView = UIView(frame: viewFrame!)
view.backgroundColor = .clear
tableView = UITableView(frame: view.frame)
tableView.delegate = self
tableView.dataSource = self
tableView.frame = subView.frame
tableView.separatorStyle = .none
tableView.estimatedRowHeight = 35
let tableViewBackground = UIImageView(image: UIImage(data: splitter?.image as! Data, scale:1.0))
tableViewBackground.contentMode = .scaleAspectFit
tableViewBackground.frame = tableView.frame
tableView.backgroundView = tableViewBackground
subView.addSubview(tableView)
view.addSubview(subView)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (splitter?.items?.count)! > 0 {
return (splitter!.items?.count)!
} else {
TableViewHelper.EmptyMessage("\(splitter?.name!) has no items to pay for.", tableView: tableView)
return 0
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
tableView.register(SplitterCarouselItemTableViewCell.classForCoder(), forCellReuseIdentifier: "splitterCarouselItemTableViewCell")
let cell: SplitterCarouselItemTableViewCell = tableView.dequeueReusableCell(withIdentifier: "splitterCarouselItemTableViewCell") as! SplitterCarouselItemTableViewCell
let item = (splitter?.items?.allObjects as! [Item])[indexPath.row]
let count = item.billSplitters?.count
cell.backgroundColor = UIColor(netHex: 0xe9edef).withAlphaComponent(0.5)
if count! > 1 {
cell.name!.text = "\(item.name!)\nsplit \(count!) ways"
cell.price!.text = "£\(Double(item.price)/Double(count!))"
} else {
cell.name!.text = item.name!
cell.price!.text = "£\(item.price)"
}
return cell
}
}
the carousel viewcontroller:
func numberOfItems(in carousel: iCarousel) -> Int {
return allBillSplitters.count
}
func carousel(_ carousel: iCarousel, viewForItemAt index: Int, reusing view: UIView?) -> UIView {
let splitter = allBillSplitters[index]
carouselIndex = index
let splitterView = SplitterCarouselItemView(frame: CGRect(x: 0, y: 0, width: width, height: height))
let viewWidth = Int(splitterView.frame.width)
let nameLabel = SplitterCarouselItemNameLabel(frame: CGRect(x: 0, y: 0, width: viewWidth, height: 30))
let emailLabel = SplitterCarouselItemEmailLabel(frame: CGRect(x: 0, y: 30, width: viewWidth, height: 20))
let addItemButton = SplitterCarouselItemButton(frame: CGRect(x: viewWidth - 45, y: 50, width: 35, height: 35), addsItem: true)
let deleteItemButton = SplitterCarouselItemButton(frame: CGRect(x: 15, y: 50, width: 35, height: 35), addsItem: false)
let itemHeight = Int(splitterView.frame.height)
let payTotalButton = SplitterCarouselItemPayButton(frame: CGRect(x: 0, y: itemHeight - 35, width: viewWidth + 1, height: 35))
let tableViewHeight = Int(height - 130)
let frame = CGRect(x: 0, y: 85, width: viewWidth, height: tableViewHeight)
let tableView = BillSplitterTableView(frame: frame, splitter: splitter)
splitterView.addSubview(nameLabel)
splitterView.addSubview(emailLabel)
splitterView.addSubview(addItemButton)
splitterView.addSubview(deleteItemButton)
splitterView.addSubview(tableView.view)
splitterView.addSubview(payTotalButton)
nameLabel.text = "\(allBillSplitters[index].name!)"
emailLabel.text = "\(allBillSplitters[index].email!)"
payTotalButton.setTitle("Pay £\(allBillSplitters[index].total)", for: .normal)
return splitterView
}
func carousel(_ carousel: iCarousel, valueFor option: iCarouselOption, withDefault value: CGFloat) -> CGFloat {
if allBillSplitters.count > 2 {
switch option {
case .spacing:
return value * 1.05
case .fadeMin:
return 0.0
case .fadeMinAlpha:
return 0.3
case .fadeMax:
return 0.0
default:
return value
}
}
return value
}
Thanks for any help in advance