Adding 'n' number of collection view to UIStackView in swift - ios

I have a ViewController Class where I'm putting scrollView and adding stackView as the subview. Now I want to add 'n' number of collectionView inside the stackView. I'm fetching data from server, there will be a different categories. If category A has data, then I need to create one collectionView for that. And I need to handle the data of corresponding collectionView in their class itself.
class ViewController: UIViewController {
var scrollView : UIScrollView!
var stackView : UIStackView!
var responseFullData : JsonFullDataResponse!
var responseData : JsonResponse!
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
print("View Controller init")
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func viewDidAppear(_ animated: Bool) {
print("View did appear")
scrollView.contentSize = CGSize(width: stackView.frame.width, height: stackView.frame.height)
}
override func viewDidLoad() {
super.viewDidLoad()
//loading all the data
loadAllJsonData()
//setting up scroll and stack views
setUpScrollAndStackView()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print("inside layout")
scrollView.contentSize = CGSize(width: stackView.frame.width, height: stackView.frame.height)
}
func setUpScrollAndStackView() {
scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[scrollView]|", options: .alignAllCenterX, metrics: nil, views: ["scrollView": scrollView]))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[scrollView]|", options: .alignAllCenterX, metrics: nil, views: ["scrollView": scrollView]))
stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.spacing = 20.0
stackView.axis = .vertical
scrollView.addSubview(stackView)
scrollView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[stackView]|", options: NSLayoutFormatOptions.alignAllCenterX, metrics: nil, views: ["stackView": stackView]))
scrollView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[stackView]", options: NSLayoutFormatOptions.alignAllCenterX, metrics: nil, views: ["stackView": stackView]))
setUpOtherViewsInStack()
}
func setUpOtherViewsInStack() {
if responseFullData.banner?.count != 0 {
print("banner has data")
var bannerCollection : BannerCollectionView = BannerCollectionView() as! BannerCollectionView
bannerCollection.register(UINib.init(nibName: "BannerCell", bundle: nil), forCellWithReuseIdentifier: "BannerCell")
stackView.addArrangedSubview(bannerCollection)
}
}
BannerCollectionView Code
class BannerCollectionView: UICollectionView , UICollectionViewDataSource{
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int{
print("control is in datasource")
return 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "BannerCell", for: indexPath) as! BannerCell
cell.backgroundColor = UIColor.blue
return cell
}
}
How do I make this work. In which class and where I should set the delegate and datasource property of BannerCollectionView? How do I initialise this?

As I understand you need to make a for from 0 to count in responseFullData.banner.count adding a collectionView each time
if let count = responseFullData.banner?.count {
for i in 0..<count {
var bannerCollection : BannerCollectionView = BannerCollectionView() as! BannerCollectionView
bannerCollection.register(UINib.init(nibName: "BannerCell", bundle: nil), forCellWithReuseIdentifier: "BannerCell")
stackView.addArrangedSubview(bannerCollection)
}
}

I have added 3 collectionView inside the UIStackView(Vertical stack). The whole view can be scrolled vertically and the collection items can be scrolled either horizontally or vertically.
class HomePageViewController: UIViewController,HeaderViewDelegate {
var scrollView : UIScrollView!
var stackView : UIStackView!
var responseFullData : JsonFullDataResponse!
if responseFullData?.menu?.count != nil{
print("menu has data")
var menuCollection : MenuCollectionView = MenuCollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout.init())
menuCollection.backgroundColor = .white
menuCollection.menuData = (self.responseFullData.menu)!
menuCollection.dataSource = menuCollection.self
menuCollection.delegate = menuCollection.self
createCollectionViews(collectionViewClass: menuCollection, identifier: "MenuCell",height : 175)
}
if responseFullData?.hongbei?.count != nil {
print("hongbei has data")
var hongbeiCollection : HongbeiCollectionView = HongbeiCollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout.init())
hongbeiCollection.dataSource = hongbeiCollection
hongbeiCollection.delegate = hongbeiCollection
hongbeiCollection.allowsSelection = true
hongbeiCollection.backgroundColor = .white
hongbeiCollection.hongbeiData = (self.responseFullData.hongbei)!
addHeaderView(headerTitle : "Hongbei")
createCollectionViews(collectionViewClass: hongbeiCollection, identifier: "HongbeiCell",height : 150)
}
if responseFullData?.freeZone?.count != nil {
print("freezone has data")
var freezoneCollection : FreeZoneCollectionView = FreeZoneCollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout.init())
freezoneCollection.dataSource = freezoneCollection.self
freezoneCollection.delegate = freezoneCollection.self
freezoneCollection.backgroundColor = .white
freezoneCollection.freeZoneData = (self.responseFullData.freeZone)!
addHeaderView(headerTitle : "FreeZone")
createCollectionViews(collectionViewClass: freezoneCollection, identifier: "FreeZoneCell",height : 150)
}
func createCollectionViews(collectionViewClass : UICollectionView, identifier : String,height : CGFloat ){
collectionViewClass.widthAnchor.constraint(equalToConstant: self.view.frame.width).isActive = true
collectionViewClass.heightAnchor.constraint(equalToConstant: height).isActive = true
collectionViewClass.register(UINib.init(nibName: identifier, bundle: nil), forCellWithReuseIdentifier: identifier)
collectionViewClass.showsHorizontalScrollIndicator = false
collectionViewClass.showsVerticalScrollIndicator = false
stackView.addArrangedSubview(collectionViewClass)
}
func addHeaderView(headerTitle : String){
let headerView = HeaderView.instanceFromNib() as! HeaderView
headerView.widthAnchor.constraint(equalToConstant: self.view.frame.width).isActive = true
headerView.heightAnchor.constraint(equalToConstant: 20).isActive = true
headerView.headerLabel.text = headerTitle
headerView.frame = headerView.frame.offsetBy(dx: 20, dy: 0)
headerView.delegate = self
stackView.addArrangedSubview(headerView)
}
}
class MenuCollectionView: UICollectionView,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout {
var menuData : [MenuData]!
override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
super.init(frame: frame, collectionViewLayout: layout)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int{
print("Menu")
return menuData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MenuCell", for: indexPath) as! MenuCell
cell.menuLabel.text = menuData[indexPath.item].title!
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize{
return CGSize(width: (collectionView.frame.width * 0.175), height: 75)
}
I have a separate class for each collectionView in the stack, where I handle the data source and delegate methods. So I can have any number for collection items I want in each collectionView.

Related

Making animating horizontal indicator using collectionView

I'm working on App where I have to manage my horizontal indicator on CollectionviewCell, while scrolling CollectionView and by selecting any Cell.
This is what I’m looking for(Just focus on Horizontal CollectionView)
As I have implemented this But I’m not getting the exact functionality/behavior AND unable to stick horizontal indicator on collectionviewCell while scrolling. I can only stick if i make a horizontal indicator in CollectionViewCell But in this Case I’m unable to apply sliding animation.
This is what I have implemented
Here is my Code Snippet for MENUBAR
import UIKit
class MenuBar: UITableViewHeaderFooterView, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UIScrollViewDelegate {
//MARK:- Properties
let cellId = "cellId"
let menuNames = ["Recommeded", "Popular", "Free", "Trending", "Love Songs", " Free Songs"]
var horizontalBarLeftAnchorConstraint : NSLayoutConstraint?
lazy var collectionView : UICollectionView = {
let cv = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
cv.translatesAutoresizingMaskIntoConstraints = false
cv.dataSource = self
cv.delegate = self
cv.showsHorizontalScrollIndicator = false
cv.backgroundColor = UIColor.clear
return cv
}()
let horizontalView : UIView = {
let v = UIView()
v.backgroundColor = UIColor.red
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
//MARK:- default methods
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
setupCollectionView()
setupHorizontalBar()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//MARK:- Functions
private func setupCollectionView() {
if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.scrollDirection = .horizontal
}
addSubview(collectionView)
collectionView.register(MenuCell.self, forCellWithReuseIdentifier: cellId)
collectionView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
collectionView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
collectionView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
collectionView.topAnchor.constraint(equalTo: topAnchor).isActive = true
}
private func setupHorizontalBar() {
addSubview(horizontalView)
let textSize = (menuNames[0] as NSString).size(withAttributes: nil)
let cellSize = textSize.width + 50
let indicatorLineWith = 25/2
let x = (cellSize/2) - CGFloat(indicatorLineWith)
//x,y,w,h
horizontalBarLeftAnchorConstraint =
horizontalView.leftAnchor.constraint(equalTo: leftAnchor, constant: x )
horizontalBarLeftAnchorConstraint?.isActive = true
horizontalView.heightAnchor.constraint(equalToConstant: 5).isActive = true
horizontalView.widthAnchor.constraint(equalToConstant: 25).isActive = true
horizontalView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
}
//MARK:- CollectionView methods
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return menuNames.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! MenuCell
cell.menuName = menuNames[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let size = (menuNames[indexPath.row] as NSString).size(withAttributes: nil)
return CGSize(width: (size.width + 50), height: frame.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
//getting Cell size on screen
let attributes : UICollectionViewLayoutAttributes = collectionView.layoutAttributesForItem(at: indexPath)!
let indicatorSize = 25/2
let cellRect = attributes.frame
let cellFrameInSuperView = collectionView.convert(cellRect, to: collectionView)
let textSize = (menuNames[indexPath.row] as NSString).size(withAttributes: nil)
let cellSize = textSize.width + 50
let x = (CGFloat(cellFrameInSuperView.origin.x) + (cellSize/2)) - CGFloat(indicatorSize)
horizontalBarLeftAnchorConstraint?.constant = x
UIView.animate(withDuration: 0.75, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
self.layoutIfNeeded()
}, completion: nil)
}
}
Here is my code snippet for MENUCELL:-
import UIKit
//MARK:- CollectionViewBaseCell
class CollectionViewBaseCell : UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {}
}
//MARK:- MenuCell
class MenuCell : CollectionViewBaseCell {
//MARK:- Properties
var menuName : String? {
didSet {
label.text = menuName
}
}
let label : UILabel = {
let lbl = UILabel()
lbl.translatesAutoresizingMaskIntoConstraints = false
lbl.text = "Label"
return lbl
}()
//MARK:- default methods
override func setupViews() {
addSubview(label)
//x,y,w,h Constraint
label.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
}
}
TRY THIS:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == self.collectionView{
print( scrollView.contentOffset.x ) // use this contentOffset
}
}

Unable to tap a button in CollectionViewCell

I have a button embedded in a UICollectionViewCell with the below structure, when I try to tap button, neither the button UI changes nor the associated function gets called, this behaviour is seen in iOS 12 (iPhone 8) and not in iOS 10 (iPhone 5)
UITableView > UITableViewCell > UICollectionView > UICollectionViewCell > UIButton
The following screenshot shows the tableViewCell with collectionViewCells.
Below is the code:
TableView
class AppointmentTVC: UITableViewController {
var appointmentTimesCollectionTVCell = AppointmentTimesCollectionTVCell()
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
appointmentTimesCollectionTVCell.appointmentTimesToDisplay = self.appointmentTimesToDisplay
appointmentTimesCollectionTVCell.isUserInteractionEnabled = true
//appointmentTimesCollectionTVCell.selectionStyle = .none
return appointmentTimesCollectionTVCell
}
}
The below is the tableview cell that holds the collectionViewCells
class AppointmentTimesCollectionTVCell: UITableViewCell {
var notificationCenter = NotificationCenter.default
let collectionView:UICollectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout.init())
var layout:UICollectionViewFlowLayout = UICollectionViewFlowLayout.init()
let collectionViewCellId = "Cell"
var cellDimensionsArray = [CGSize]()
var appointmentTimes = [Date]()
var indexOfSelectedItem : Int?
var appointmentTimesToDisplay : [Date]{
set(appointmentTimesToSet){ // This method is called whenever appointmentTimesToDisplay is set
appointmentTimes = appointmentTimesToSet
print("appointmentTimesBeingSet: \(appointmentTimes)")
collectionView.reloadData() // This is used to reload images
}
get{
return appointmentTimes
}
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
override init(style: UITableViewCellStyle, reuseIdentifier: String!) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// Configure CollectionView
collectionView.register(ButtonCVCell.self, forCellWithReuseIdentifier: collectionViewCellId)
collectionView.isScrollEnabled = true // To scroll collectionview inside cell
layout.scrollDirection = .horizontal
layout.minimumInteritemSpacing = CGFloat(10.0) // The minimum spacing to use between items in the same row.
layout.minimumLineSpacing = CGFloat(10.0) // The minimum spacing to use between lines of items in the grid.
collectionView.setCollectionViewLayout(layout, animated: true)
collectionView.backgroundColor = Colors.white
collectionView.delegate = self as UICollectionViewDelegate
collectionView.dataSource = self as UICollectionViewDataSource
collectionView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(collectionView)
let viewsDict = [
"collectionView" : collectionView
] as [String : Any]
// collectionView Constraints
contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-5-[collectionView]-5-|", options: [], metrics: nil, views: viewsDict))
contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-5-[collectionView]-5-|", options: [], metrics: nil, views: viewsDict))
}
}
extension AppointmentTimesCollectionTVCell: UICollectionViewDelegateFlowLayout {
// FOR SETTING FIXED SIZES
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// setting the standard widths of 80 and height 30.0 as default to ensure correct collection view cell size
// These have been obtained by printing intrinsicContent size width in function cellforItem at
if cellDimensionsArray.count == 0 { return CGSize(width:80.0, height: 30.0) }
let _cellDimension = cellDimensionsArray[indexPath.item]
return _cellDimension
}
}
extension AppointmentTimesCollectionTVCell: UICollectionViewDataSource {
// MARK: UICollectionViewDataSource
func numberOfSections(in collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return appointmentTimes.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let item = indexPath.item
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: collectionViewCellId, for: indexPath) as? ButtonCVCell
let appointmentTimeObject = appointmentTimes[item]
let appointmentTimeString = " " + DateFormatter().standardDateFormatter.string(from: appointmentTimeObject) + " "
print("indexOfSelectedItem: \(indexOfSelectedItem), item: \(item)")
// This is to ensure that only the selected item color stays and remainder are set to default
if indexOfSelectedItem == item{
cell?.button.backgroundColor = Colors.curieBlue
cell?.button.layer.borderColor = Colors.curieBlue.cgColor
cell?.button.setTitleColor(Colors.white, for: .normal)
} else{
cell?.button.layer.borderWidth = 1
cell?.button.layer.borderColor = Colors.lightGrey.cgColor
cell?.button.backgroundColor = Colors.white
cell?.button.setTitleColor(Colors.curieBlue, for: .normal)
}
cell?.button.tag = item
cell?.button.setTitle(appointmentTimeString , for: .normal)
//cell?.button.addTarget(self, action: #selector(setSelectedTime(sender:)), for: .touchUpInside)
cell?.button.addTarget(self, action: #selector(setSelectedTime(sender:)), for: .touchUpInside)
print("buttonTagSetForTheButton: \(cell)")
if let intrinsicContentSize = cell?.button.intrinsicContentSize {
//let intrinsicContentSize = cell?.button.intrinsicContentSize
let cellWidth = intrinsicContentSize.width
let cellHeight = intrinsicContentSize.height
let cellDimension = CGSize(width: cellWidth, height: cellHeight)
if cellDimensionsArray.count <= appointmentTimes.count{ // Setting dimensions for new cells
cellDimensionsArray.append(cellDimension)
}else{ // Replacing dimensions for existing cells
cellDimensionsArray[item] = cellDimension
}
}
return cell ?? UICollectionViewCell()
//return cell
}
func setSelectedTime(sender: UIButton){
print("setSelectedTimeFuncCalled")
let selectedTime = appointmentTimes[sender.tag]
self.indexOfSelectedItem = sender.tag
let timestamp = selectedTime.timeIntervalSince1970 // Get the Unix timestamp
// Posting a local notification
let selectedAppointmentTimeDict = ["selectedAppointmentTime" : Int(timestamp)]
notificationCenter.post(name: Notification.Name(NotificationNames.appointmentTimeSelected), object: nil, userInfo: selectedAppointmentTimeDict)
self.collectionView.reloadData()
}
}
The below code is used to produce collection view cells with buttons.
class ButtonCVCell: UICollectionViewCell {
let button : UIButton = {
let btn = UIButton(type: .system)
btn.tintColor = Colors.curieBlue
btn.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
btn.layer.cornerRadius = 5
btn.sizeToFit()
return btn
}()
var cellWidthConstraint : NSLayoutConstraint!
var cellWidth : CGFloat{
set(width){
self.cellWidthConstraint.constant = width
self.cellWidthConstraint.isActive = true
print("SettingCellsWidth: \(width)")
}
get{
return self.cellWidthConstraint.constant
}
}
override func awakeFromNib() {
super.awakeFromNib()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
fatalError("init(coder:)")
}
override init(frame: CGRect) {
super.init(frame: frame)
self.contentView.translatesAutoresizingMaskIntoConstraints = false
self.cellWidthConstraint = NSLayoutConstraint(item: contentView, attribute: .width, relatedBy: .equal, toItem: nil, attribute:.notAnAttribute, multiplier: 0, constant: 0.0)
button.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(button)
let viewsDict = [
"button" : button
] as [String : Any]
// Button Constraints
contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[button(30)]|", options: [], metrics: nil, views: viewsDict))
contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[button]", options: [], metrics: nil, views: viewsDict))
}
}
Each collection view cell is button. Use collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) instead
I think the problem is you are trying to use AutoLayout in the CollectionViewCell's ContentView, but CollectionViews use frames.
It is fine if you use AutoLayout for your Button inside the Cell, but the ContentView should resize by itself.
Try removing these lines:
// ButtonCVCell
var cellWidthConstraint : NSLayoutConstraint!
var cellWidth : CGFloat{
set(width){
self.cellWidthConstraint.constant = width
self.cellWidthConstraint.isActive = true
print("SettingCellsWidth: \(width)")
}
get{
return self.cellWidthConstraint.constant
}
}
And these:
// ButtonCVCell override init(frame: CGRect)
self.contentView.translatesAutoresizingMaskIntoConstraints = false
self.cellWidthConstraint = NSLayoutConstraint(item: contentView, attribute: .width, relatedBy: .equal, toItem: nil, attribute:.notAnAttribute, multiplier: 0, constant: 0.0)

Swift array append not working on collectionView reload data

Inside of my collectionView cell i have a nested collection view. It is first updated after making an api call (didSet), but in a load more mechanism when i try to append data to the data source array and reload the collectionView..the changes are not reflected. Here is my code
class PhotosCollectionViewController:
UICollectionViewCell,UICollectionViewDelegate,
UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
let network = networkingService()
let keychain = KeychainSwift()
var userID: String?
var code: String?
var platesID: String!{
didSet{
let parameters: Parameters = [:]
network.makeGetRequestDecodable(parameters: parameters, url:
"endpoint") { (reponse) in
do {
let modeled = try JSONDecoder().decode([Model].self, from:reponse)
self.gps_singleton_posts = modeled
print("Reload Method Called")
self.appsCollectionView?.reloadData()
} catch let err {
print(err)
}
}
}
}
var gps_singleton_posts: [gps_sing]?
let gridId = "gridID"
var appsCollectionView: UICollectionView? = {
let layout = UICollectionViewFlowLayout()
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 3
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.backgroundColor = UIColor.white
collectionView.translatesAutoresizingMaskIntoConstraints = false
return collectionView
}()
override init(frame: CGRect) {
super.init(frame: frame)
if let value = keychain.get("userID") {
self.userID = value
// print("keychain\(self.userID!)")
}
if let value = keychain.get("security_token") {
self.code = value
// print("keychain\(self.code!)")
}
appsCollectionView?.dataSource = self
appsCollectionView?.delegate = self
appsCollectionView?.contentInset = UIEdgeInsetsMake(60, 0, 0, 0)
appsCollectionView?.scrollIndicatorInsets = UIEdgeInsetsMake(60,
0, 0, 0)
addSubview(appsCollectionView!);
addConstraintsWithFormat("H:|[v0]|", views: appsCollectionView!)
addConstraintsWithFormat("V:|[v0]|", views: appsCollectionView!)
appsCollectionView?.register(ImageCell.self,
forCellWithReuseIdentifier: gridId)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 123, height: 123)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print(gps_singleton_posts)
if let count = gps_singleton_posts?.count{
return count
}
return 0
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let parameters: Parameters = [:]
network.makeGetRequestDecodable(parameters: parameters, url: "Endpoiint") { (reponse) in
do {
let modeled = try JSONDecoder().decode([gps_sing].self, from:reponse)
for model in modeled{
self.gps_singleton_posts?.append(model)
}
print("Reload Method Called")
collectionView.reloadData()
print(self.gps_singleton_posts)
} catch let err {
print(err)
}
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
print(self.gps_singleton_posts?.count)
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: gridId, for: indexPath)
as! ImageCell
cell.backgroundColor = UIColor.red
cell.singlePost = gps_singleton_posts![indexPath.row]
return cell
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
scrollView.isScrollEnabled = false
}
}
class ImageCell: UICollectionViewCell {
var imageViewGrid: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.frame = CGRect(x: 0, y:0 , width: 100, height: 100)
imageView.image = UIImage(named: "delete")
imageView.clipsToBounds = true
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
var textLabel: UILabel = {
let lv = UILabel()
lv.textColor = UIColor.red
lv.translatesAutoresizingMaskIntoConstraints = false
return lv
}()
var singlePost: gps_sing! {
didSet{
let imageUrl = singlePost.uid
print(imageUrl!)
imageViewGrid.setImageWith(NSURL(string: imageUrl!)! as URL)
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setUpViews()
}
func setUpViews(){
addSubview(imageViewGrid)
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": imageViewGrid]))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": imageViewGrid]))
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
In the above code (didSelectAt) function is used to load more items..but the changes are not reflected. Pleas help me out. Thanks a lot for answering my question.
So, i found the solution to my problem. In the above collection view i had disabled scrolling..because of which the added content after reload data was not showing up. Once i enabled scrolling the data started to show up.
first of all: NEVER do layout changes in a parallel thread.
I'm seeing that in platesID's didSet you are doing an API call and in its response you're trying to reload a collectionview. This kind of operations must be done in the main thread! Try to wrap the collectionview.reloadData() in this way
DispatchQueue.main.async { [weak self] in
self?.appsCollectionView?.reloadData()
}
I don't know if this will solve all your problems but the reloadData() action may not work properly in a secondary thread.
Layout operations must always be launched on the main thread.

Subclass UICollectionViewCell not showing

I am new to Swift, and I intend to programmatically create a UICollectionView with instances of a subclass of UICollectionViewCell. However, the cells themselves do not show up.
The majority of the following code is derived from this tutorial: iOS Swift: Build UICollectionView programmatically without Storyboards.
This is how I create the UICollectionViewController:
let flowLayout = UICollectionViewFlowLayout()
let productView = ProductView(collectionViewLayout: flowLayout)
productView.setSizeOfCollectionView(width, height: (height * 0.7))
middleLeftView.addSubview(productView.view)
And these are my subclasses:
class ProductView : UICollectionViewController, UICollectionViewDelegateFlowLayout {
let customCellIdentifier = "customCellIdentifier"
var products = [[String]]()
var product1 = ["beer", "2,50"]
var product2 = ["wine", "3,50"]
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = UIColor.redColor()
collectionView?.delegate = self
collectionView?.dataSource = self
products.append(product1)
products.append(product2)
collectionView?.registerClass(Product.self, forCellWithReuseIdentifier: customCellIdentifier)
self.collectionView?.reloadData()
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let product = collectionView.dequeueReusableCellWithReuseIdentifier(customCellIdentifier, forIndexPath: indexPath) as! Product
product.specifiyProduct(products[indexPath.item])
product.backgroundColor = UIColor.blueColor()
return product
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print(products.count) // prints 2
return products.count
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return CGSizeMake(300, 300)
}
func setSizeOfCollectionView(width: CGFloat, height: CGFloat){
print(width) // prints 1022.5
print(height) // prints 702.8
collectionView?.frame = CGRectMake(10, 10, (width - 20), (height - 20))
self.collectionView?.reloadData()
}
}
class Product : UICollectionViewCell {
override init(frame: CGRect){
super.init(frame: frame)
self.backgroundColor = UIColor.yellowColor()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func specifiyProduct(info: [String]){
let nameLabel = UILabel(frame: CGRectMake(0, 0, 50, 50))
nameLabel.backgroundColor = UIColor.grayColor()
nameLabel.text = info[0] + info[1]
nameLabel.translatesAutoresizingMaskIntoConstraints = false
addSubview(nameLabel)
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[v0]|" , options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": nameLabel]))
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[v0]|" , options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": nameLabel]))
}
}
The UICollectionView shows up, but the UICollectionViewCells do not. Does anyone know how to fix this?
Try to change
collectionView?.registerClass(Product.self, forCellWithReuseIdentifier: customCellIdentifier)
with
collectionView?.register(Product.self, forCellWithReuseIdentifier: customCellIdentifier)
The method you use to register cell has been renamed to the later one. That's might be the cause.

iOS CollectionView Header doesn't show

Im trying to add a segmented control onto my header but I am having trouble doing this. In this example to make things simpler I am adding a Label but that is also not showing. Can someone tell me why this is not happening?
import UIKit
class SessionsViewController: UICollectionViewController , UICollectionViewDelegateFlowLayout {
override func viewDidLoad() {
super.viewDidLoad()
prepareCollectionView()
view.backgroundColor = UIColor.white
navigationController?.navigationBar.isTranslucent = true
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Log Out", style: .plain, target: self, action: #selector(handleLogout))
navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(named: "plus") , style: .plain, target: self, action: nil)
navigationItem.rightBarButtonItem?.tintColor = UIColor.honePalette.accent
}
func prepareSegmentedControll()->UISegmentedControl{
let items = ["Future Sessions", "Past Sessions"]
let control = UISegmentedControl(items: items)
control.selectedSegmentIndex = 0
control.tintColor = UIColor.honePalette.accent
let font = UIFont.systemFont(ofSize: 12)
control.setTitleTextAttributes([NSFontAttributeName: font], for: .normal)
return control
}
func prepareCollectionView(){
collectionView?.backgroundColor = UIColor.white
collectionView?.alwaysBounceVertical = true
collectionView?.register(sessionsInfo.self, forCellWithReuseIdentifier: "cellId")
}
// return of the number per item per section
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
//this is when the collection is clicked
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let indexPath = collectionView.indexPathsForSelectedItems
print("this is index path:", indexPath)
}
// this is the cell of the collection returning initialized with the SessionsInfo
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let myCell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as? sessionsInfo
myCell?.sessionLabel.text = "cell \(indexPath.row)"
return myCell!
}
// this is when the size of the cell returns
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width - 10, height: 80)
}
// return supplementary view
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "header", for: indexPath)
let label = UILabel(frame: headerView.bounds)
label.text = "Top View"
label.font = UIFont(name: "helvetica", size: 12)
label.textAlignment = .center
headerView.addSubview(label)
//headerView.headerLabel.text = "header"
return headerView
}
func handleLogout(){
BaseServices.baseServices.signOut()
present(LoginViewController(), animated: true, completion: nil)
}
}
class headerInfo : UICollectionReusableView{
override init(frame: CGRect) {
super.init(frame: frame)
setupHeader()
}
var headerLabel : UILabel = {
let label = UILabel()
label.text = "HEADER"
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupHeader(){
backgroundColor = UIColor.honePalette.raindrops
addSubview(headerLabel)
addConstraints(NSLayoutConstraint.constraints( withVisualFormat: "H:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0":headerLabel]))
addConstraints(NSLayoutConstraint.constraints( withVisualFormat: "V:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0":headerLabel]))
}
}
class sessionsInfo: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupSessions()
}
var sessionLabel : UILabel = {
let label = UILabel()
label.text = "sessionView"
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
var sessionTimeLabel : UILabel = {
let label = UILabel()
label.text = ""
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
var sessionLocationLabel : UILabel = {
let label = UILabel()
label.text = ""
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
func setupSessions(){
backgroundColor = UIColor.honePalette.raindrops
addSubview(sessionLabel)
addConstraints(NSLayoutConstraint.constraints( withVisualFormat: "H:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0":sessionLabel]))
addConstraints(NSLayoutConstraint.constraints( withVisualFormat: "V:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0":sessionLabel]))
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I tried your code in a new project, and you are missing two key steps: registering the supplementary view and setting the size of your header.
In your prepareCollectionView function, you also need to register the header view:
collectionView?.register(headerInfo.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "header")
You also need to implement another delegate function giving the size of your header view:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: 100, height: 100) // fix to be your intended size
}
Here is proof that it works. (I didn't have access to your custom colors, which is why everything is orange and gray.)

Resources