UICollectionView Section headers & footers show wrong way - iOS - swift

The problem is:
SupplementaryView has a textLabel on it, but the text appears at a wrong location while header and footer and textLabel just show right.
As you can see in the picture, only the first section header has a title, other header or footers text is out of sight(they are on the screen but you have to scroll down to see it).
I don't know how to solve it...
I setup my collectionview like following in viewDidLoad function:
func setupCollectionsView(){
let width:CGFloat = self.view.frame.size.width/4
let flowLayout = UICollectionViewFlowLayout()
flowLayout.minimumInteritemSpacing = 5
flowLayout.minimumLineSpacing = 5
flowLayout.estimatedItemSize = CGSizeMake(width, width)
flowLayout.headerReferenceSize = CGSizeMake(self.view.frame.size.width, 30)
flowLayout.footerReferenceSize = CGSizeMake(self.view.frame.size.width, 30)
flowLayout.scrollDirection = UICollectionViewScrollDirection.Vertical
collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: flowLayout)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.registerClass(FuncViewCell.self, forCellWithReuseIdentifier: "collectionView")
collectionView.backgroundColor = UIColor.clearColor()
collectionView.registerClass(SupplementaryView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "headerView")
collectionView.registerClass(SupplementaryView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, withReuseIdentifier: "footerView")
self.view.addSubview(collectionView)
}
For each supplementaryview I write this:
func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
var view:SupplementaryView?
var title:String = ""
switch kind {
case UICollectionElementKindSectionFooter:
view = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: "footerView", forIndexPath: indexPath) as? SupplementaryView
view?.backgroundColor = UIColor ( red: 0.9879, green: 0.3225, blue: 0.4925, alpha: 1.0 )
title = "Footer"
default:
view = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: "headerView", forIndexPath: indexPath) as? SupplementaryView
view?.backgroundColor = UIColor ( red: 0.6571, green: 1.0, blue: 0.8628, alpha: 1.0 )
title = "Header"
}
switch indexPath.section {
case 0:
title = "First \(title)"
default:
title = "Second \(title)"
}
view?.setTitle(title)
return view!
}
And my supplementary view implemented this way:
class SupplementaryView: UICollectionReusableView{
var titleLabel:UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
titleLabel = UILabel(frame: frame)
self.addSubview(titleLabel)
let gesture = UITapGestureRecognizer(target: self, action: #selector(SupplementaryView.tap))
self.addGestureRecognizer(gesture)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setTitle(title:String){
print("Label's frame is\(titleLabel.frame)")
titleLabel.text = title
}
func tap(){
print("\(titleLabel.text!) tapped")
}}
PS:I print the frame of each textlabel, they are just in right postion.
.

Change you code as below, and to see if it works
override init(frame: CGRect) {
super.init(frame: frame)
// There should be self.bounds
titleLabel = UILabel(frame: self.bounds)
self.addSubview(titleLabel)
let gesture = UITapGestureRecognizer(target: self, action: #selector(SupplementaryView.tap))
self.addGestureRecognizer(gesture)
}

Related

Section Header Collectionview

I have tried to add a section header to my grid collectionview but I keep getting this error.
Thread 1: "the view returned from -collectionView:viewForSupplementaryElementOfKind:atIndexPath: was not retrieved by calling -dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath: for element kind 'UICollectionElementKindSectionFooter' at index path <NSIndexPath: 0xfa8d71d0dca3589b> {length = 2, path = 0 - 0}; supplementary view: <UICollectionReusableView: 0x7fba7296bdc0; frame = (0 0; 0 0); layer = <CALayer: 0x6000013f48a0>>"
My SectionHeader
class SectionHeader: UICollectionReusableView {
var label: UILabel = {
let label: UILabel = UILabel()
label.textColor = KSColor.neutral400.getColor()
label.lineSpacing = -0.12
label.font = .sfProTextMedium(size: 12)
label.sizeToFit()
return label
}()
// MARK: Object lifecycle
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.topAnchor.constraint(equalTo: self.topAnchor, constant: 16).isActive = true
label.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 16).isActive = true
label.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 16).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
My collectionview controller
class SelfServiceCollectionViewController: UICollectionViewController {
func setUpCollectionView() {
gridCollectionView.register(UINib(nibName: "EmptyCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "emptyCell")
gridCollectionView.register(UINib(nibName: "SelfServiceRootCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "selfServiceRootCell")
gridCollectionView.register(UINib(nibName: "SelfServiceChildCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "selfServiceChildCell")
gridCollectionView.register(SectionHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "header")
}
// MARK: Header
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
if kind == UICollectionView.elementKindSectionHeader {
let sectionHeader = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "header", for: indexPath) as! SectionHeader
sectionHeader.label.text = parentVC.selfServiceArray[indexPath.section].categoryName.uppercased()
return sectionHeader
} else {
return UICollectionReusableView()
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
if parentVC.selfServiceArray.isEmpty {
return CGSize(width: 0, height: 0)
} else {
if parentVC.selfServiceArray[section].categoryName == "" {
return CGSize(width: 0, height: 0)
} else {
return CGSize(width: 250, height: 50)
}
}
}
When I remove
if kind == UICollectionView.elementKindSectionHeader {
from code I get this error instead of previous one
Thread 1: "could not dequeue a view of kind: UICollectionElementKindSectionFooter with identifier header - must register a nib or a class for the identifier or connect a prototype cell in a storyboard"
I did not create a nib file for it. Why does it need it? I am adding label programmatically.
I registered both header and footer
gridCollectionView.register(SectionHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "header")
gridCollectionView.register(SectionHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: "header")
then separated kind to a switch case as below
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionView.elementKindSectionHeader:
let sectionHeader = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "header", for: indexPath) as! SectionHeader
sectionHeader.label.text = parentVC.selfServiceArray[indexPath.section].categoryName.uppercased()
return sectionHeader
case UICollectionView.elementKindSectionFooter:
let sectionFooter = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "header", for: indexPath) as! SectionHeader
sectionFooter.label.text = ""
return sectionFooter
default:
assert(false, "Unexpected element kind")
}
}
worked

Label with text not showing in custom cell

I have a custom cell set up for UICollectionView. I have a Data model that has an image and title.
I can get the image to show in the cell but cant get the table to show
I tried to set up the label programmatically inside the custom cell (in the same way that the image is set up) yet the image shows but label is invisible...
class CustomCell: UICollectionViewCell {
var data: CustomData? {
didSet {
guard let data = data else {return}
bg.image = data.image
titleLabel.text = data.title
}
}
fileprivate var titleLabel : UILabel = {
let label = UILabel()
label.textAlignment = .left
label.lineBreakMode = .byWordWrapping
label.numberOfLines = 1
label.textColor = .black
label.font = UIFont (name: "Helvetica Neue", size: 30)
label.text = "random text"
return label
}()
fileprivate let bg : UIImageView = {
let iv = UIImageView()
iv.image = #imageLiteral(resourceName: "pic1")
iv.translatesAutoresizingMaskIntoConstraints = false
iv.contentMode = .scaleAspectFill
iv.layer.cornerRadius = 12
iv.layer.masksToBounds = true
iv.clipsToBounds = true
return iv
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(titleLabel)
contentView.addSubview(bg)
bg.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
bg.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
bg.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
bg.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
this is how the custom cell is set up and below how cellForItemAt is setup
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CustomCell
let color = colorList[indexPath.row]
cell.backgroundColor = color
cell.contentView.layer.masksToBounds = true;
cell.layer.cornerRadius = 12
cell.clipsToBounds = true
return cell
}
any ideas where am I going wrong?
added frame as #rmaddy suggested to titleLable
Try
titleLabel.translatesAutoresizingMaskIntoConstraints = false

Adding an Image to UICollection Header Swift

I'm looking to implement an image within the header of a collection view. This is the code that I have so far, but no header appears when I test. Am I missing something?
func collectionView(_ collectionView: UICollectionView, viewForHeaderInSection section: Int) -> UIView? {
let view = UIView()
let image = UIImageView()
image.frame = CGRect(x: collectionView.frame.width - 10 , y: 0, width: 20, height: 20)
image.image = UIImage.init(named: "trophyCase")
view.addSubview(image)
return view
}
UICollectionViewDelegate doesn't offer such a method as viewForHeaderInSection
Instead you can use viewForSupplementaryElementOfKind method of UICollectionViewDataSource
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
guard kind == UICollectionView.elementKindSectionHeader else {
fatalError("Unrecognized element of kind: \(kind)")
}
let view: ReusableHeaderView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: kind, for: indexPath) as! ReusableHeaderView
view.imageView.image = UIImage.init(named: "trophyCase")
view.imageView.frame = CGRect(x: collectionView.frame.width - 10 , y: 0, width: 20, height: 20)
return view
}
You are also required to register elementKindSectionHeader
collectionView.register(ReusableHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: UICollectionView.elementKindSectionHeader)
Following will be your ReusableHeaderView
class ReusableHeaderView: UICollectionReusableView {
let imageView = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
self.setupUI()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setupUI()
}
private func setupUI() {
self.addSubview(imageView)
// Instead of settings imageView.frame, add following to use autolayout constraints
self.imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
imageView.topAnchor.constraint(equalTo: self.topAnchor),
imageView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -10.0),
imageView.widthAnchor.constraint(equalToConstant: 20.0),
imageView.heightAnchor.constraint(equalToConstant: 20.0)
])
}
}
Create a view subclassing UICollectionReusableView:
class HeaderView: UICollectionReusableView {
let imageView: UIImageView = {
let iv = UIImageView(image: /*put your image here*/)
iv.clipsToBounds = true
iv.contentMode = .scaleAspectFill
return iv
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
addSubview(imageView)
imageView.fillSuperview() // Check extension below
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Then in your ViewController, first create a reuseIdentifier for your view:
fileprivate let headerId = "headerId"
After that register your custom view in collectionView(lets do it in viewDidLoad):
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(HeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: headerId)
}
Declare your custom view as optional in your vc:
var headerView: HeaderView?
Then override viewForSupplementaryElementOfKind method of the collectionView to initialize headerView:
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerId, for: indexPath) as? HeaderView
return headerView!
}
Then implement another collectionView method to give your headerView a size:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return .init(width: view.frame.width, height: 340) // edit width and height as you please.
}
Extension for fillSuperView used in initialization of custom view:
extension UIView {
func fillSuperview(withPadding padding: UIEdgeInsets = .zero) {
translatesAutoresizingMaskIntoConstraints = false
if let superview = superview {
topAnchor.constraint(equalTo: superview.topAnchor, constant: padding.top).isActive = true
leftAnchor.constraint(equalTo: superview.leftAnchor, constant: padding.left).isActive = true
rightAnchor.constraint(equalTo: superview.rightAnchor, constant: -padding.right).isActive = true
bottomAnchor.constraint(equalTo: superview.bottomAnchor, constant: -padding.bottom).isActive = true
}
}
}
Now it should work as header view for your collectionView.

Swift 3: Be able to tap view behind a collectionViewCell?

I am working on a new feature in my app where I want a user to have an image and then be able to swipe through a collectionView on top of that image, adding "filters". The issue that I have is that I want the user to still be able to interact with the image, but it is behind the collectionView. If I set the collectionViews inUsersInteractionEnabled to false, of course I then cannot use the collectionView at all. I am going for a similar functionality to Snapchats "scroll through filters" feature. Any help will be highly appreciated. Thank you guys. Code is below:
import UIKit
class FirstCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
func setupViews() {
backgroundColor = .clear
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class SecondCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
let cellView: UIView = {
let view = UIView()
view.backgroundColor = .red
view.alpha = 0.25
return view
}()
func setupViews() {
backgroundColor = .clear
addSubview(cellView)
cellView.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class SecondController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
layout.scrollDirection = .horizontal
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .clear
cv.dataSource = self
cv.delegate = self
cv.translatesAutoresizingMaskIntoConstraints = false
cv.isPagingEnabled = true
return cv
}()
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(FirstCell.self, forCellWithReuseIdentifier: "firstCell")
collectionView.register(SecondCell.self, forCellWithReuseIdentifier: "secondCell")
view.backgroundColor = .white
setupViews()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: view.frame.height)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.item == 0 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "firstCell", for: indexPath) as! FirstCell
return cell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "secondCell", for: indexPath) as! SecondCell
return cell
}
}
lazy var imageView: UIImageView = {
let iv = UIImageView()
let image = UIImage(named: "img")
iv.image = image
iv.contentMode = .scaleAspectFill
iv.translatesAutoresizingMaskIntoConstraints = false
iv.isUserInteractionEnabled = true
iv.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTap)))
return iv
}()
func handleTap() {
print(123)
}
func setupViews() {
view.addSubview(imageView)
view.addSubview(collectionView)
imageView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
imageView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
imageView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
collectionView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
collectionView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
collectionView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
}
}
There is a hitTest(_::) method available to override in any of the UIView subclass, which you can implement in your custom UICollectionView subclass and forward taps from it to any other view (the image behind collection view in your case).
override func hitTest(_ point: CGPoint,
with event: UIEvent?) -> UIView? {
let hitView = super.hitTest(point, with: event)
if self.parentViewController.forwardTapToImageIfNeeded(point) {
return nil
}
return hitView
}
Where parentViewController is a reference to your SecondController instance, which should have forwardTapToImageIfNeeded(_) function to check if user's tap is on top of the image frame, and if it is — automatically pass tap event to it.
func forwardTapToImageIfNeeded(_ p: CGPoint) -> Bool {
/* Convert point to local coordinates */
let pointInSuperView = self.convert(p, from: collectionView)
/* Check if tap is on top of the image frame */
if imageBehindCollection.frame.contains(pointInSuperView) {
// Forward tap to the image `imageBehindCollection`
return true
}
return false
}
Note: I didn't test this particular code by myself, so it might need a few fixes in Xcode.

UICollectionView self sizing cells. Insert/reloadData breaks layout

Writing this question as my last resort, being stuck on this for few days now. I am trying to implement self sizing cells for UICollectionView with FlowLayout. Everything seem to work fine until I reladData() or do insertItemsAtIndexPaths() while being at the bottom of the collectionView.
This is my Cell:
class FooFoo: UICollectionViewCell {
var label: UILabel!
var text: String! {
didSet {
label.text = text
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
setupConstraints()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupView() {
self.contentView.backgroundColor = UIColor.yellowColor()
label = ({
let view = UILabel()
view.setTranslatesAutoresizingMaskIntoConstraints(false)
self.contentView.addSubview(view)
return view
})()
}
func setupConstraints() {
self.contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-100-[childView]-100-|", options: nil, metrics: nil, views: ["childView": label]))
self.contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-100-[childView]-100-|", options: nil, metrics: nil, views: ["childView": label]))
}
}
This is CollectionView definition:
collectionView = ({
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 1
layout.estimatedItemSize = CGSize(width: 300, height: 300)
let view = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
view.dataSource = self
view.delegate = self
view.registerClass(FooFoo.self, forCellWithReuseIdentifier: "Cell")
view.backgroundColor = UIColor.clearColor()
self.view.addSubview(view)
return view
})()
This is dataSource:
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! FooFoo
cell.text = "FooBar: \(indexPath.item)"
return cell
}
After I fetch additional items, i tried both inserting new array of indexes, or reloading data and the result is the same - broken layout.
I also tried replacing estimatedItemSize with itemSize and it did work just fine, so I am pretty certain its because self-sizing cells.
This is the result: