Cells won't show in UICollectionView - ios

I'm trying to create a UICollectionView and display few cells there.
This is my code:
class MainVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
var mForecast = [CustomCollectionViewCell]()
let CVCellIdentifier = "forecastCell"
lazy var mCollectionView: UICollectionView = {
var collectionView = UICollectionView(frame: CGRect(x: 0, y: 0, width: 300, height: 150), collectionViewLayout: UICollectionViewFlowLayout())
collectionView.clipsToBounds = true
collectionView.backgroundColor = .red
collectionView.translatesAutoresizingMaskIntoConstraints = false
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(red: 80/255, green: 135/255, blue: 179/255, alpha: 1.0)
setupNavBar()
self.navigationItem.searchController = mSearchBarController
setupMainWeatherIcon()
fillArrayWithData()
mCollectionView.register(CustomCollectionViewCell.self, forCellWithReuseIdentifier: CVCellIdentifier)
mCollectionView.dataSource = self
mCollectionView.delegate = self
}
private func fillArrayWithData(){
for _ in 1...6 {
let forecastCell: ForecastCell = ForecastCell()
forecastCell.mDayLabel = "DAY-TEST"
forecastCell.mWeatherIcon = UIImage(named: "partly-cloudy")
forecastCell.mTempLabel = "TEMP-TEST"
mForecast.append(forecastCell)
}
mCollectionView.reloadData()
}
//MARK: COLLECTION VIEW METHODS
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return mForecast.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = mCollectionView.dequeueReusableCell(withReuseIdentifier: CVCellIdentifier, for: indexPath) as! CustomCollectionViewCell
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: (view.frame.width / 6) - 16 , height: 70)
}
func collectionView(_ collectionView: UICollectionView, layout
collectionViewLayout: UICollectionViewLayout, insetForSectionAt
section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 10, left: 8, bottom: 10, right: 8)
}
}
This is the CustomCollectionViewCell class:
import UIKit
class CustomCollectionViewCell: UICollectionViewCell {
var mDayLabel: String?
var mWeatherIcon: UIImage?
var mTempLabel: String?
let dayTV: UILabel = {
var label = UILabel()
label.textAlignment = .center
label.font = UIFont.boldSystemFont(ofSize: 12)
label.textColor = .blue
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let weatherImg: UIImageView = {
var img = UIImageView()
img.translatesAutoresizingMaskIntoConstraints = false
return img
}()
let tempLabel: UILabel = {
var label = UILabel()
label.textAlignment = .center
label.font = UIFont.systemFont(ofSize: 8)
label.textColor = .blue
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
if let label = mDayLabel{
dayTV.text = label
}
if let image = mWeatherIcon{
weatherImg.image = image
}
if let temp = mTempLabel{
tempLabel.text = temp
}
setupDayTextView()
setupWeatherImage()
setupTempLabel()
}
private func setupDayTextView(){
addSubview(dayTV)
dayTV.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
dayTV.topAnchor.constraint(equalTo: self.topAnchor, constant: 10).isActive = true
}
private func setupWeatherImage(){
addSubview(weatherImg)
weatherImg.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
weatherImg.topAnchor.constraint(equalTo: dayTV.bottomAnchor, constant: 10).isActive = true
}
private func setupTempLabel(){
addSubview(tempLabel)
tempLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
tempLabel.topAnchor.constraint(equalTo: weatherImg.bottomAnchor, constant: 10).isActive = true
}
}
Of course I have a method to fill mForecast array with data, if I add a subview I do see the cell, but I know I shouldn't do that to view the cells inside the collection view.
I tried looking but couldn't find what's wrong here and why the cells won't be displayed.
This is what I get

ORIGINAL:
After setting the delegate and the datasource, you need to call collectionView.reloadData()
REVISED:
You are calling fillArrayWithData, which calls reloadData before you finish configuring the collectionView's datasource and delegate. Thus, when reloadData is called, there is no source that sets the data and loads the cells.
Try calling your fillArrayWithData after you finalize the configuration of your collection view.
I personally recommend configuring your collection view in viewDidLoad or in the didSet property observer of collectionView:
var collectionView: UICollectionView! {
didSet {
collectionView.delegate = self
collectionView.dataSource = self
}
}
And then I initiate the load of the data in my viewWillAppear method.
EXAMPLE:
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(red: 80/255, green: 135/255, blue: 179/255, alpha: 1.0)
setupNavBar()
self.navigationItem.searchController = mSearchBarController
setupMainWeatherIcon()
// This is where you are calling fillArrayWithData right now.
// fillArrayWithData()
mCollectionView.register(CustomCollectionViewCell.self, forCellWithReuseIdentifier: CVCellIdentifier)
mCollectionView.dataSource = self
mCollectionView.delegate = self
// This is where you should be calling fillArrayWithData
fillArrayWithData()
}

Related

how to add delegate method into stack view. please

I'm a junior developer in South Korea.
first of all, my English skill is not so good. I'm sorry.
I hava a question about UIViewController Delegate method
I just want to write featureList from UIViewController to UICollectionViewCell using FeatureDetailViewController
here my code
final class AppViewController: UIViewController {
private var featureList: [Feature] = []
private let scrollView = UIScrollView()
private let contentView = UIView()
private lazy var stackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.distribution = .equalSpacing
stackView.spacing = 0.0
let featureSectionView = FeatureSectionView(frame: .zero)
let rankingFeatureSectionView = RankingFeatureSectionView(frame: .zero)
let exchangeCodeButtonView = ExchangeCodeButtonView(frame: .zero)
let spacingView = UIView()
spacingView.snp.makeConstraints {
$0.height.equalTo(100.0)
}
[
featureSectionView,
rankingFeatureSectionView,
exchangeCodeButtonView,
spacingView
].forEach {
stackView.addArrangedSubview($0)
}
return stackView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(contentView)
setupNavigationController()
setupLayout()
}
}
private extension AppViewController {
func setupNavigationController() {
navigationItem.title = "앱"
navigationItem.largeTitleDisplayMode = .always
navigationController?.navigationBar.prefersLargeTitles = true
}
func setupLayout() {
view.addSubview(scrollView)
scrollView.snp.makeConstraints {
$0.top.equalTo(view.safeAreaLayoutGuide.snp.top)
$0.bottom.leading.trailing.equalToSuperview()
}
scrollView.addSubview(contentView)
contentView.snp.makeConstraints {
$0.edges.equalToSuperview()
$0.width.equalToSuperview()
}
contentView.addSubview(stackView)
stackView.snp.makeConstraints {
$0.edges.equalToSuperview()
}
}
}
I just want to present this FeatureDetailViewController when imageview is tapped which is in FeatureSectionCollectionViewCell
here is FeatureSectionCollectionViewCell
final class FeatureSectionCollectionViewCell: UICollectionViewCell {
private lazy var typeLabel: UILabel = {
let label = UILabel()
label.textColor = .systemBlue
label.font = .systemFont(ofSize: 12.0, weight: .semibold)
return label
}()
private lazy var appNameLabel: UILabel = {
let label = UILabel()
label.textColor = .label
label.font = .systemFont(ofSize: 20.0, weight: .bold)
return label
}()
private lazy var descriptionLabel: UILabel = {
let label = UILabel()
label.textColor = .secondaryLabel
label.font = .systemFont(ofSize: 16.0, weight: .semibold)
return label
}()
private lazy var imageview: UIImageView = {
let imageView = UIImageView()
imageView.layer.cornerRadius = 7.0
imageView.layer.borderWidth = 0.5
imageView.clipsToBounds = true
return imageView
}()
func setup(feature: Feature) {
setupLayout()
typeLabel.text = feature.type
appNameLabel.text = feature.appName
descriptionLabel.text = feature.description
imageview.backgroundColor = .lightGray
if let imageURL = URL(string: feature.imageURL) {
imageview.kf.setImage(with: imageURL)
}
}
}
private extension FeatureSectionCollectionViewCell {
func setupLayout() {
[
typeLabel,
appNameLabel,
descriptionLabel,
imageview
].forEach {
addSubview($0)
}
}
}
and UIView named as FeatureSectionView below
final class FeatureSectionView: UIView {
//data from plist
private var featureList: [Feature] = []
private lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.isPagingEnabled = true
collectionView.backgroundColor = .systemBackground
collectionView.showsHorizontalScrollIndicator = false
collectionView.register(
FeatureSectionCollectionViewCell.self,
forCellWithReuseIdentifier: "FeatureSectionCollectionViewCell"
)
return collectionView
}()
private let separatorView = SeparatorView(frame: .zero)
//FeatureSectionView이 UIView 이므로 ViewDidLoad에서 하지 않고 init 에서 실행
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
fetchData() //reloadData after read data
collectionView.reloadData()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension FeatureSectionView: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return featureList.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FeatureSectionCollectionViewCell", for: indexPath) as? FeatureSectionCollectionViewCell
let feature = featureList[indexPath.row]
cell?.setup(feature: feature)
return cell ?? UICollectionViewCell()
}
}
extension FeatureSectionView : UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
CGSize(width: collectionView.frame.width - 32.0, height: frame.width)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
UIEdgeInsets(top: 0.0, left: 16.0, bottom: 0.0, right: 16.0)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 32.0
}
}
private extension FeatureSectionView {
func setupViews() {
[
collectionView,
separatorView
].forEach {
addSubview($0)
}
collectionView.snp.makeConstraints {
$0.leading.trailing.bottom.equalToSuperview()
$0.top.equalToSuperview().inset(16.0)
$0.height.equalTo(snp.width)
}
separatorView.snp.makeConstraints {
$0.leading.trailing.bottom.equalToSuperview()
$0.top.equalTo(collectionView.snp.bottom).offset(16.0)
}
}
func fetchData() {
guard let url = Bundle.main.url(forResource: "Feature", withExtension: "plist") else { return }
do {
let data = try Data(contentsOf: url)
let result = try PropertyListDecoder().decode([Feature].self, from: data)
featureList = result
} catch { }
}
}
HOW TO SET DELEGATE METHOD to presentation FeatureDetailViewController when imageview was tapped ?
I know it's hard to read, but I almost half-day-spent. please teach me guys.
thx
The delegate method would be useful if the user taps the cell in general; however, you want it to transition only when the imageView in the cell is tapped--from what I am understanding.
So, what you need to do is add a tapGestureRecognizer onto your imageView and perform the transition with a selector.
First create a method of the functionality that you want (this will be the selector)
#objc func imageWasTapped(tapGestureRecognizer: UITapGestureRecognizer) {
DispatchQueue.main.async {
//perform transition here
}
}
Add a tapGestureRecognizer to the imageView
private lazy var imageview: UIImageView = {
let imageView = UIImageView()
imageView.layer.cornerRadius = 7.0
imageView.layer.borderWidth = 0.5
imageView.clipsToBounds = true
//Make sure it can be touched
imageView.isUserInteractionEnabled = true
//Create the Gesture Recognizer and initialize it with the selector
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(imageWasTapped(tapGestureRecognizer:)))
//add the Gesture Recognizer to the view
imageView.addGestureRecognizer(tapGestureRecognizer)
return imageView
}()

How to connect UIPageControl and CollectionView?

I have collection view with Custom View Cell. There are scroll view and three image view in a custom view cell.
My ViewController has UIPageControll, but I don't know how to connect UIPageControll and scroll view.
My code
ViewController:
class MainScrenenViewController: UIViewController {
let data = [
CustomData(title: "A", backgroundImage: #imageLiteral(resourceName: "Onboard")),
CustomData(title: "B", backgroundImage: #imageLiteral(resourceName: "Onboard")),
CustomData(title: "B", backgroundImage: #imageLiteral(resourceName: "Onboard")),
]
//UIPage Controller
lazy var pageControl: UIPageControl = {
let pageControl = UIPageControl()
pageControl.numberOfPages = data.count
pageControl.translatesAutoresizingMaskIntoConstraints = false
pageControl.addTarget(self, action: #selector(pageControlTapHandler(sender:)), for: .touchUpInside)
return pageControl
}()
var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.register(DayWeatherCell.self, forCellWithReuseIdentifier: "sliderCell")
collectionView.layer.cornerRadius = 5
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.backgroundColor = UIColor(red: 0.125, green: 0.306, blue: 0.78, alpha: 1)
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .brown
view.addSubview(collectionView)
view.addSubview(pageControl)
collectionView.dataSource = self
collectionView.delegate = self
setupConstraints()
}
//MARK: ~FUNCTIONS
func setupConstraints() {
let constraints = [
collectionView.widthAnchor.constraint(equalToConstant: 344),
collectionView.heightAnchor.constraint(equalToConstant: 212),
collectionView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16),
collectionView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16),
collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 112),
pageControl.topAnchor.constraint(equalTo: cityLabel.bottomAnchor, constant: 10),
pageControl.widthAnchor.constraint(equalToConstant: 100),
pageControl.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
]
NSLayoutConstraint.activate(constraints)
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
//Selector for UIPage Controller
#objc func pageControlTapHandler(sender: UIPageControl) {
//I don't know what I need to do here
}
extension MainScrenenViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "sliderCell", for: indexPath) as! DayWeatherCell
cell.backgroundColor = .red
return cell
}
}
extension MainScrenenViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("User tapped on item \(indexPath.row)")
}
}
extension MainScrenenViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
}
}
extension MainScrenenViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: collectionView.frame.height)
}
}
My CollectionViewCell:
class DayWeatherCell: UICollectionViewCell, UIScrollViewDelegate {
weak var mainScreenViewController: MainScrenenViewController?
var data: CustomData? {
didSet {
guard let data = data else { return }
imageView.image = data.backgroundImage
}
}
var imageView: UIImageView = {
let imageView = UIImageView()
imageView.image = #imageLiteral(resourceName: "Onboard")
imageView.layer.cornerRadius = 5
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
lazy var scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.showsHorizontalScrollIndicator = false
scrollView.isPagingEnabled = true
scrollView.delegate = self
return scrollView
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(scrollView)
contentView.addSubview(imageView)
self.contentView.layer.cornerRadius = 10
let constraints = [
scrollView.topAnchor.constraint(equalTo: contentView.topAnchor),
scrollView.leftAnchor.constraint(equalTo: contentView.leftAnchor),
scrollView.rightAnchor.constraint(equalTo: contentView.rightAnchor),
scrollView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
imageView.topAnchor.constraint(equalTo: contentView.topAnchor),
imageView.leftAnchor.constraint(equalTo: contentView.leftAnchor),
imageView.rightAnchor.constraint(equalTo: contentView.rightAnchor),
imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
]
NSLayoutConstraint.activate(constraints)
}
required init?( coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
enter image description here
You can use this. In this code block, collection view cell size equal to collection view and collection view scroll horizontally. I hope, this helps you.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offSet = scrollView.contentOffset.x
let width = scrollView.frame.width
let horizontalCenter = width / 2
pageControl.currentPage = Int(offSet + horizontalCenter) / Int(width)
}

Invalid Selector Using Delegate Pattern

I am attempting to use the delegate pattern to animate a change in height for a collectionView. The button that triggers this change is in the header. However when I press the button not only does the height not change but it also crashes with the error
'NSInvalidArgumentException', reason: '-[UIButton length]: unrecognized selector sent to instance 0x12f345b50'
I feel like I have done everything right but it always crashes when I click the button. Does anyone see anything wrong and is there anyway that I can animate the change in height for the cell the way I want it to. This is the cell class along with the protocol and the delegate.
import Foundation
import UIKit
protocol ExpandedCellDelegate:NSObjectProtocol{
func viewEventsButtonTapped(indexPath:IndexPath)
}
class EventCollectionCell:UICollectionViewCell {
var headerID = "headerID"
weak var delegateExpand:ExpandedCellDelegate?
public var indexPath:IndexPath!
var eventArray = [EventDetails](){
didSet{
self.eventCollectionView.reloadData()
}
}
var enentDetails:Friend?{
didSet{
var name = "N/A"
var total = 0
seperator.isHidden = true
if let value = enentDetails?.friendName{
name = value
}
if let value = enentDetails?.events{
total = value.count
self.eventArray = value
seperator.isHidden = false
}
if let value = enentDetails?.imageUrl{
profileImageView.loadImage(urlString: value)
}else{
profileImageView.image = imageLiteral(resourceName: "Tokyo")
}
self.eventCollectionView.reloadData()
setLabel(name: name, totalEvents: total)
}
}
let container:UIView={
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.cornerRadius = 16
view.layer.borderColor = UIColor.lightGray.cgColor
view.layer.borderWidth = 0.3
return view
}()
//profile image view for the user
var profileImageView:CustomImageView={
let iv = CustomImageView()
iv.layer.masksToBounds = true
iv.layer.borderColor = UIColor.lightGray.cgColor
iv.layer.borderWidth = 0.3
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
//will show the name of the user as well as the total number of events he is attending
let labelNameAndTotalEvents:UILabel={
let label = UILabel()
label.textColor = .black
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
return label
}()
let seperator:UIView={
let view = UIView()
view.backgroundColor = .lightGray
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
//collectionview that contains all of the events a specific user will be attensing
lazy var eventCollectionView:UICollectionView={
let flow = UICollectionViewFlowLayout()
flow.scrollDirection = .vertical
let spacingbw:CGFloat = 5
flow.minimumLineSpacing = 0
flow.minimumInteritemSpacing = 0
let cv = UICollectionView(frame: .zero, collectionViewLayout: flow)
//will register the eventdetailcell
cv.translatesAutoresizingMaskIntoConstraints = false
cv.backgroundColor = .white
cv.register(EventDetailsCell.self, forCellWithReuseIdentifier: "eventDetails")
cv.register(FriendsEventsViewHeader.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: headerID)
cv.delegate = self
cv.dataSource = self
cv.backgroundColor = .blue
cv.contentInset = UIEdgeInsetsMake(spacingbw, 0, spacingbw, 0)
cv.showsVerticalScrollIndicator = false
cv.bounces = false
return cv
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.setUpCell()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setLabel(name:String,totalEvents:Int){
let mainString = NSMutableAttributedString()
let attString = NSAttributedString(string:name+"\n" , attributes: [NSAttributedStringKey.foregroundColor:UIColor.black,NSAttributedStringKey.font:UIFont.systemFont(ofSize: 14)])
mainString.append(attString)
let attString2 = NSAttributedString(string:totalEvents == 0 ? "No events" : "\(totalEvents) \(totalEvents == 1 ? "Event" : "Events")" , attributes: [NSAttributedStringKey.foregroundColor:UIColor.darkGray,NSAttributedStringKey.font:UIFont.italicSystemFont(ofSize: 12)])
mainString.append(attString2)
labelNameAndTotalEvents.attributedText = mainString
}
}
//extension that handles creation of the events detail cells as well as the eventcollectionview
//notice the delegate methods
//- Mark EventCollectionView DataSource
extension EventCollectionCell:UICollectionViewDataSource{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return eventArray.count
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerID, for: indexPath) as! FriendsEventsViewHeader
header.viewEventsButton.addTarget(self, action: #selector(viewEventsButtonTapped), for: .touchUpInside)
return header
}
#objc func viewEventsButtonTapped(indexPath:IndexPath){
print("View events button touched")
if let delegate = self.delegateExpand{
delegate.viewEventsButtonTapped(indexPath: indexPath)
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier:"eventDetails" , for: indexPath) as! EventDetailsCell
cell.details = eventArray[indexPath.item]
cell.backgroundColor = .yellow
cell.seperator1.isHidden = indexPath.item == eventArray.count-1
return cell
}
}
//- Mark EventCollectionView Delegate
extension EventCollectionCell:UICollectionViewDelegateFlowLayout{
//size for each indvidual cell
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: 50)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: collectionView.frame.width, height: 40)
}
}
This is the view that ultimately is supposed to be handling the expansion via the delegate function.
import UIKit
import Firebase
class FriendsEventsView: UIViewController,UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout {
var friends = [Friend]()
var followingUsers = [String]()
var notExpandedHeight : CGFloat = 100
var expandedHeight : CGFloat?
var isExpanded = [Bool]()
//so this is the main collectonview that encompasses the entire view
//this entire view has eventcollectionCell's in it which in itself contain a collectionview which also contains cells
//so I ultimately want to shrink the eventCollectionView
lazy var mainCollectionView:UICollectionView={
// the flow layout which is needed when you create any collection view
let flow = UICollectionViewFlowLayout()
//setting the scroll direction
flow.scrollDirection = .vertical
//setting space between elements
let spacingbw:CGFloat = 5
flow.minimumLineSpacing = spacingbw
flow.minimumInteritemSpacing = 0
//actually creating collectionview
let cv = UICollectionView(frame: .zero, collectionViewLayout: flow)
//register a cell for that collectionview
cv.register(EventCollectionCell.self, forCellWithReuseIdentifier: "events")
cv.translatesAutoresizingMaskIntoConstraints = false
//changing background color
cv.backgroundColor = .red
//sets the delegate of the collectionView to self. By doing this all messages in regards to the collectionView will be sent to the collectionView or you.
//"Delegates send messages"
cv.delegate = self
//sets the datsource of the collectionView to you so you can control where the data gets pulled from
cv.dataSource = self
//sets positon of collectionview in regards to the regular view
cv.contentInset = UIEdgeInsetsMake(spacingbw, 0, spacingbw, 0)
return cv
}()
//label that will be displayed if there are no events
let labelNotEvents:UILabel={
let label = UILabel()
label.textColor = .lightGray
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.font = UIFont.italicSystemFont(ofSize: 14)
label.text = "No events found"
label.isHidden = true
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
//will set up all the views in the screen
self.setUpViews()
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: imageLiteral(resourceName: "close_black").withRenderingMode(.alwaysOriginal), style: .done, target: self, action: #selector(self.goBack))
}
func setUpViews(){
//well set the navbar title to Friends Events
self.title = "Friends Events"
view.backgroundColor = .white
//adds the main collection view to the view and adds proper constraints for positioning
view.addSubview(mainCollectionView)
mainCollectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
mainCollectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
mainCollectionView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0).isActive = true
mainCollectionView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
//adds the label to alert someone that there are no events to the collectionview and adds proper constrains for positioning
mainCollectionView.addSubview(labelNotEvents)
labelNotEvents.centerYAnchor.constraint(equalTo: mainCollectionView.centerYAnchor, constant: 0).isActive = true
labelNotEvents.centerXAnchor.constraint(equalTo: mainCollectionView.centerXAnchor, constant: 0).isActive = true
//will fetch events from server
self.fetchEventsFromServer()
}
// MARK: CollectionView Datasource for maincollection view
//will let us know how many eventCollectionCells tht contain collectionViews will be displayed
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print(friends.count)
isExpanded = Array(repeating: false, count: friends.count)
return friends.count
}
//will control the size of the eventCollectionCells that contain collectionViews
height is decided for the collectionVIew here
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let event = friends[indexPath.item]
if let count = event.events?.count,count != 0{
notExpandedHeight += (CGFloat(count*40)+10)
}
self.expandedHeight = notExpandedHeight
if isExpanded[indexPath.row] == true{
return CGSize(width: collectionView.frame.width, height: expandedHeight!)
}else{
return CGSize(width: collectionView.frame.width, height: 100)
}
}
//will do the job of effieicently creating cells for the eventcollectioncell that contain eventCollectionViews using the dequeReusableCells function
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "events", for: indexPath) as! EventCollectionCell
cell.backgroundColor = UIColor.orange
cell.indexPath = indexPath
cell.delegateExpand = self
cell.enentDetails = friends[indexPath.item]
return cell
}
}
extension FriendsEventsView:ExpandedCellDelegate{
func viewEventsButtonTapped(indexPath:IndexPath) {
isExpanded[indexPath.row] = !isExpanded[indexPath.row]
print(indexPath)
UIView.animate(withDuration: 0.8, delay: 0.0, usingSpringWithDamping: 0.9, initialSpringVelocity: 0.9, options: UIViewAnimationOptions.curveEaseInOut, animations: {
self.mainCollectionView.reloadItems(at: [indexPath])
}, completion: { success in
print("success")
})
}
}
I used this post for reference to implement
Expandable UICollectionViewCell
This is a very common mistake.
The passed parameter in a target / action selector is always the affected UI element which triggers the action in your case the button.
You cannot pass an arbitrary object for example an indexPath because there is no parameter in the addTarget method to specify that arbitrary object.
You have to declare the selector
#objc func viewEventsButtonTapped(_ sender: UIButton) {
or without a parameter
#objc func viewEventsButtonTapped() {
UIControl provides a third syntax
#objc func viewEventsButtonTapped(_ sender: UIButton, withEvent event: UIEvent?) {
Any other syntax is not supported.

Want to push to another view controller from a container view

What do i have: A view controller (WhereViewController) with some "child/container"-views. One is a collection view (typeCollectionView). the WhereViewController is embedded in a navigationController:
Screenshot
class WhereViewController: UIViewController {
lazy var progressContainerView: ProgressBarView = {
let containerView = ProgressBarView(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
containerView.translatesAutoresizingMaskIntoConstraints = false
return containerView
}()
let questionLabel: UILabel = {
let label = UILabel()
label.text = "question?"
label.textAlignment = NSTextAlignment.center
label.font = UIFont.init(name: "OpenSans-Italic", size: 14)
label.textColor = UIColor(red:0.33, green:0.33, blue:0.33, alpha:1.0)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let typeCollectionView: WhereCollectionView = {
let collectionView = WhereCollectionView(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
collectionView.translatesAutoresizingMaskIntoConstraints = false
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Schaden melden"
view.backgroundColor = UIColor.white
view.addSubview(progressContainerView)
view.addSubview(questionLabel)
view.addSubview(typeCollectionView)
setupProgressContainerView()
setupQuestionLabel()
setupTypeCollectionView()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
private func setupProgressContainerView() {
progressContainerView.topAnchor.constraint(equalTo: view.topAnchor, constant: 60).isActive = true
progressContainerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
progressContainerView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
progressContainerView.heightAnchor.constraint(equalToConstant: 50).isActive = true
}
private func setupQuestionLabel() {
questionLabel.topAnchor.constraint(equalTo: progressContainerView.bottomAnchor, constant: 22).isActive = true
questionLabel.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
questionLabel.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
questionLabel.heightAnchor.constraint(equalToConstant: 20).isActive = true
}
private func setupTypeCollectionView() {
typeCollectionView.topAnchor.constraint(equalTo: questionLabel.bottomAnchor, constant: 20).isActive = true
typeCollectionView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
typeCollectionView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
typeCollectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
}
Want am I trying to do: Within the typeCollectionView: If an item is selected (didSelectItemAt) i want to push to the next viewController via navigationController of the WhereViewController. My typeCollectionView looks like:
class WhereCollectionView: UICollectionView, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
var whereViewController: WhereViewController?
let label = ["x", "y", "z"]
override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
let layout = UICollectionViewFlowLayout()
super.init(frame: CGRect.zero, collectionViewLayout: layout)
backgroundColor = UIColor.white
delegate = self
dataSource = self
self.register(WhereCollectionViewCell.self, forCellWithReuseIdentifier: "WhereCollectionViewCell")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
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 label.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "WhereCollectionViewCell", for: indexPath) as! WhereCollectionViewCell
cell.injureLabel.text = label[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: self.frame.width, height: 75)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
}
}
I've tried to give the whereViewController to the typeCollectionView:
let typeCollectionView: WhereCollectionView = {
let collectionView = WhereCollectionView(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.whereViewController = self
return tableView
}()
But get: Cannot assign value of type '(NSObject) -> () -> WhereViewController' to type 'WhereViewController?'
Could someone help me? or isn't it possible to "speak" with the navigationController from the collectionView (containerView)?
I do not use storyboard
Try to:
1) Add extension:
extension UIView {
var parentViewController: UIViewController? {
var parentResponder: UIResponder? = self
while parentResponder != nil {
parentResponder = parentResponder!.next
if parentResponder is UIViewController {
return parentResponder as! UIViewController!
}
}
return nil
}
}
2) Now you can use:
parentViewController?.present(<ControllerName>, animated: true)
Hope it helps
You should conform the view controller to the collection delegate methods and not the collection view itself.
Make WhereViewController conform to UICollectionViewDataSource, UICollectionViewDelegate and UICollectionViewDelegateFlowLayout:
class WhereViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout
In WhereViewController's viewDidLoad method, set up the delegate and the data source:
override func viewDidLoad() {
super.viewDidLoad()
typeCollectionView.dataSource = self
typeCollectionView.delegate = self
[...]
}
Here you can use delegate to send message from CollectionView to
ViewController.
Simple declare a protocol
protocol CustomDelegate: class {
func collectionView(DidSelect : Int)
}
After that make an instance of CustomDelegate protocol in WhereCollectionView Class
class WhereCollectionView: UICollectionView {
weak var customDelegate:CustomDelegate?
}
and in didSelect methods fire the delegate method
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let _del = customDelegate {
_del.collectionView(DidSelect : IndexPath.row)
}
}
In ViewController there you are adding collectionview in viewcontroller set delegate to self
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Schaden melden"
view.backgroundColor = UIColor.white
typeCollectionView.customDelegate = self
view.addSubview(progressContainerView)
view.addSubview(questionLabel)
view.addSubview(typeCollectionView)
setupProgressContainerView()
setupQuestionLabel()
setupTypeCollectionView()
}
Also Implement the Custom delegate method in ViewController
func collectionView(DidSelect : Int) {
/// from there you can do your job
}

How to define dynamic heights for UICollectionViewCells with a TextView inside

This is a popular question but I'm still not able to find a simple answer of how to define a dynamic height for a UICollectionViewCell with a TextView inside. I found this answer UICollectionView - dynamic cell height? but I'm not using interface and I would like a solution for interfaces built in code.
import UIKit
class ResumeController : UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
let cellId = "cellId"
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
//layout.estimatedItemSize = CGSize(width: 100, height: 100)
layout.minimumLineSpacing = 0
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .white
cv.dataSource = self
cv.delegate = self
cv.translatesAutoresizingMaskIntoConstraints = false
cv.register(FirmCell.self, forCellWithReuseIdentifier: self.cellId)
return cv
}()
override func viewDidLoad() {
view.backgroundColor = .white
setupViews()
}
func setupViews() {
setupCollectionView()
}
func setupCollectionView() {
view.addSubview(collectionView)
collectionView.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor).isActive = true
collectionView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
collectionView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
collectionView.bottomAnchor.constraint(equalTo: bottomLayoutGuide.topAnchor).isActive = true
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
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: view.frame.width, height: 100.0)
}
}
class FirmCell : UICollectionViewCell {
let imageView: UIImageView = {
let iv = UIImageView()
iv.backgroundColor = .orange
// iv.image = somelogo
iv.contentMode = .scaleAspectFill
iv.clipsToBounds = true
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
lazy var textView: UITextView = {
let tv = UITextView()
tv.isEditable = false
tv.isSelectable = false
tv.isScrollEnabled = false
tv.attributedText = self.attributedText()
tv.translatesAutoresizingMaskIntoConstraints = false
tv.layer.borderWidth = 0.5
tv.layer.borderColor = UIColor.blue.cgColor
return tv
}()
let separatorLine: UIView = {
let view = UIView()
view.backgroundColor = UIColor(white: 0.9, alpha: 1)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
func attributedText() -> NSAttributedString {
let text = NSMutableAttributedString()
// company
var companyAttr = [String: Any]()
companyAttr[NSFontAttributeName] = UIFont.systemFont(ofSize: 15, weight: UIFontWeightBold)
companyAttr[NSForegroundColorAttributeName] = UIColor.black
text.append(NSAttributedString(string: "line1" , attributes: companyAttr))
// period
var periodAttr = [String: Any]()
periodAttr[NSFontAttributeName] = UIFont.systemFont(ofSize: 14)
periodAttr[NSForegroundColorAttributeName] = UIColor.lightGray
text.append(NSAttributedString(string: " line1" , attributes: periodAttr))
// body
var bodyAttr = [String: Any]()
bodyAttr[NSFontAttributeName] = UIFont.systemFont(ofSize: 14, weight: UIFontWeightThin)
bodyAttr[NSForegroundColorAttributeName] = UIColor.black
text.append(NSAttributedString(string: "\na huge amount of text goes here ... trying to figure out a way to have different collectionview cells row height", attributes: bodyAttr))
return text
}
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
setupImageView()
}
func setupImageView() {
addSubview(imageView)
imageView.topAnchor.constraint(equalTo: topAnchor, constant: 9.0).isActive = true
imageView.leftAnchor.constraint(equalTo: leftAnchor, constant: 9.0).isActive = true
imageView.heightAnchor.constraint(equalToConstant: 40.0).isActive = true
imageView.widthAnchor.constraint(equalToConstant: 40.0).isActive = true
setupTextView()
}
func setupTextView() {
addSubview(textView)
textView.topAnchor.constraint(equalTo: imageView.topAnchor, constant: 0).isActive = true
textView.leftAnchor.constraint(equalTo: imageView.rightAnchor, constant: 7.0).isActive = true
textView.rightAnchor.constraint(equalTo: rightAnchor, constant: -9.0).isActive = true
textView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
}
Could someone provide some pointers?

Resources