How to define dynamic heights for UICollectionViewCells with a TextView inside - ios

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?

Related

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)
}

How to pressed a UIButton within a customized TableviewCell?

I have been looking all throughout SO on how to interact with a UIButton within a customized tableview cell. All of the answers I have seen are using IBOutlets, however I have not seen a way to do this fully programmatically. I am use to interacting with buttons via button.addTarget. Here are my two ViewControllers, one being the customized tableviewcell and the other being the ViewController.
Here is my customized. I tried using a protocol delegate route, however this has failed.
import UIKit
#objc protocol TableViewNew {
func onClickCell()
}
class NewMoveTableViewCell: UITableViewCell {
var cellDelegate: TableViewNew?
static let identifier = "NewTableViewCell"
private let myImageView: UIImageView = {
let imageView = UIImageView()
imageView.clipsToBounds = true
imageView.contentMode = .scaleAspectFill
imageView.layer.masksToBounds = true
imageView.backgroundColor = .purple
imageView.layer.cornerRadius = 80/2
return imageView
}()
private let myLabel : UILabel = {
let label = UILabel()
label.text = "test"
label.backgroundColor = .blue
label.textColor = .systemPink
label.adjustsFontSizeToFitWidth = true
label.textAlignment = .center
return label
}()
private let button: UIButton = {
let button = UIButton()
button.setTitle("Invite", for: .normal)
button.backgroundColor = .systemPink
button.layer.cornerRadius = 10
return button
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
addSubview(myImageView)
addSubview(myLabel)
addSubview(button)
setImageConstratins()
setTitleLabelConstraints()
setButton()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setImageConstratins() {
myImageView.translatesAutoresizingMaskIntoConstraints = false
myImageView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
myImageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12).isActive = true
myImageView.heightAnchor.constraint(equalToConstant: 80).isActive = true
myImageView.widthAnchor.constraint(equalToConstant: 80).isActive = true
}
func setTitleLabelConstraints() {
myLabel.translatesAutoresizingMaskIntoConstraints = false
myLabel.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
myLabel.leadingAnchor.constraint(equalTo: myImageView.trailingAnchor, constant: 5).isActive = true
myLabel.heightAnchor.constraint(equalToConstant: 80).isActive = true
//myLabel.trailingAnchor.constraint(equalTo: button.leadingAnchor, constant: -12).isActive = true
}
func setButton() {
button.translatesAutoresizingMaskIntoConstraints = false
button.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
button.leadingAnchor.constraint(equalTo: myLabel.trailingAnchor, constant: -5).isActive = true
button.heightAnchor.constraint(equalToConstant: 80).isActive = true
button.widthAnchor.constraint(equalToConstant: 150).isActive = true
button.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12).isActive = true
}
public func configure(with name: String, label: String) {
myLabel.text = label
myImageView.image = UIImage(named: name)
}
override func prepareForReuse() {
super.prepareForReuse()
myLabel.text = nil
myImageView.image = nil
}
#objc func didTapButton(_ sender: Any) {
cellDelegate?.onClickCell()
}
}
Secondly, here is the ViewController that the TableView is within.
import UIKit
class NewMoveViewController: UIViewController {
private let tableView: UITableView = {
let tableView = UITableView()
tableView.rowHeight = 100
return tableView
}()
private var collectionView: UICollectionView?
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.itemSize = CGSize(width: 50, height: 50)
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView?.register(NewMoveCollectionViewCell.self, forCellWithReuseIdentifier: NewMoveCollectionViewCell.identifier)
collectionView?.showsHorizontalScrollIndicator = false
title = "Add to Group"
tableView.register(NewMoveTableViewCell.self, forCellReuseIdentifier: NewMoveTableViewCell.identifier)
tableView.delegate = self
tableView.dataSource = self
collectionView?.backgroundColor = .systemBlue
collectionView?.dataSource = self
collectionView?.delegate = self
guard let myCollection = collectionView else {
return
}
view.addSubview(myCollection)
// Do any additional setup after loading the view.
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView?.frame = CGRect(x: 0, y: 100, width: view.frame.size.width, height: 50)
tableView.frame = CGRect(x: 0, y: 200, width: view.frame.size.width, height: view.frame.size.height)
}
}
extension NewMoveViewController : UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: NewMoveTableViewCell.identifier, for: indexPath) as! NewMoveTableViewCell
cell.cellDelegate = self
cell.configure(with: "", label: "test")
return cell
}
func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
return false
}
}
extension NewMoveViewController : UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NewMoveCollectionViewCell.identifier, for: indexPath) as! NewMoveCollectionViewCell
return cell
}
}
extension NewMoveViewController : TableViewNew {
func onClickCell() {
print("Pressed")
}
I conformed this ViewController to the protocol from the customized cell and put the function within the cell's cellForRowAt function. When I use this route, I run my app and everything comes up fine, however when I try to click on the customized tableviewcell within the viewcontroller, nothing happens. Any help would be greatly appreciated.
you need to add the action handler to your button:
button.addTarget(self, action: #selector(didTapButton(sender:)), for: .touchUpInside)

UICollectionView weird cells animation when dismiss mode of the keyboard is interactive

I'm trying to achieve a chat system using UICollectionView -The collectionview is turned upside down and so are the cells using CGAffineTransform(scaleX: -1, y: 1)- and so far everything is going well, I'm calculating cell sizes manually using NSString.boundingRect and it's working as expected, then I enabled the collectionview's interactive keyboard dismissal since then whenever I drag the keyboard a little bit then I let go it results into a really weird animation which I can't seem to figure out what's triggering it.
I also use Typist which is a small utility class that facilitates keyboard handling https://github.com/totocaster/Typist, I'm not really suspecting it since it is a simple wrapper UIKeyboard notifications.
I've tried several stuff including but not limited to
1-Disabling animations when showing cell using
UIView.setAnimationsEnabled(false) then enabling it again just before returning the cell which did not work.
2-Removing the upside-down approach altogether, and again nope even when it's in a normal transform the bug still occurs
3-Rewrote the entire UICollectionView implementation to UITableView implementation which -surprisingly- yielded a better result the weird animation isn't as strong as it's on the UICollectionView but it's still happening to some extent
4-Removing Typist altogether and reverting back to NotificationCenter and manual handling and nope, does not work.
UICollectionView initialization
lazy var chatCollectionView: UICollectionView = {
let collectionViewFlowLayout = UICollectionViewFlowLayout()
collectionViewFlowLayout.scrollDirection = .vertical
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewFlowLayout)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.backgroundColor = .white
collectionView.showsHorizontalScrollIndicator = false
collectionView.showsVerticalScrollIndicator = false
collectionView.register(OutgoingTextCell.self, forCellWithReuseIdentifier: OutgoingTextCell.className)
collectionView.register(IncomingTextCell.self, forCellWithReuseIdentifier: IncomingTextCell.className)
collectionView.register(OutgoingImageCell.self, forCellWithReuseIdentifier: OutgoingImageCell.className)
collectionView.register(IncomingImageCell.self, forCellWithReuseIdentifier: IncomingImageCell.className)
collectionView.transform = CGAffineTransform(scaleX: 1, y: -1)
collectionView.semanticContentAttribute = .forceLeftToRight
collectionView.keyboardDismissMode = .interactive
return collectionView
}()
UICollectionView DataSource / Delete implementation
extension ChatViewController: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return presenter.numberOfRows
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if presenter.isSender(at: indexPath) {
switch presenter.messageType(at: indexPath) {
case .text:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: OutgoingTextCell.className, for: indexPath) as! OutgoingTextCell
presenter.configure(cell: AnyConfigurableCell(cell), at: indexPath)
return cell
case .photo:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: OutgoingImageCell.className, for: indexPath) as! OutgoingImageCell
presenter.configure(cell: AnyConfigurableCell(cell), at: indexPath)
return cell
}
} else {
switch presenter.messageType(at: indexPath) {
case .text:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: IncomingTextCell.className, for: indexPath) as! IncomingTextCell
presenter.configure(cell: AnyConfigurableCell(cell), at: indexPath)
return cell
case .photo:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: IncomingImageCell.className, for: indexPath) as! IncomingImageCell
presenter.configure(cell: AnyConfigurableCell(cell), at: indexPath)
return cell
}
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if presenter.messageType(at: indexPath) == .photo {
return CGSize(width: view.frame.width, height: 250)
} else {
if let height = cachedSizes[presenter.uuidForMessage(at: indexPath)] {
return CGSize(width: view.frame.width, height: height)
} else {
let height = calculateTextHeight(at: indexPath)
cachedSizes[presenter.uuidForMessage(at: indexPath)] = height
return CGSize(width: view.frame.width, height: height)
}
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0)
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if indexPath.row == presenter.numberOfRows - 1 && !isPaginating {
presenter.loadNextPage()
}
}
private func calculateTextHeight(at indexPath: IndexPath) -> CGFloat {
let approximateSize = CGSize(width: view.frame.width - 88, height: .greatestFiniteMagnitude)
let estimatedHeight = NSString(string: presenter.contentForMessage(at: indexPath)).boundingRect(with: approximateSize, options: .usesLineFragmentOrigin, attributes: [.font: DinNextFont.regular.getFont(ofSize: 14)], context: nil).height
return estimatedHeight + 40
}
}
Cell implementation
class OutgoingTextCell: UICollectionViewCell, ConfigurableCell {
lazy var containerView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .coral
view.layer.cornerRadius = 10
return view
}()
lazy var contentLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = .white
label.numberOfLines = 0
label.font = DinNextFont.regular.getFont(ofSize: 14)
return label
}()
private lazy var stackView: UIStackView = {
let stackView = UIStackView(arrangedSubviews: [timeLabel, checkMarkImageView])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .horizontal
stackView.distribution = .equalSpacing
stackView.alignment = .center
stackView.spacing = 4
return stackView
}()
private lazy var checkMarkImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "ic_check")?.withRenderingMode(.alwaysTemplate))
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.tintColor = .white
imageView.heightAnchor.constraint(equalToConstant: 8).isActive = true
imageView.widthAnchor.constraint(equalToConstant: 10).isActive = true
return imageView
}()
lazy var timeLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = .white
label.font = DinNextFont.regular.getFont(ofSize: 10)
label.text = "12:14"
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
layoutUI()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func addSubviews() {
addSubview(containerView)
containerView.addSubview(contentLabel)
containerView.addSubview(stackView)
}
func setupContainerViewConstraints() {
NSLayoutConstraint.activate([
containerView.topAnchor.constraint(equalTo: topAnchor, constant: 0),
containerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8),
containerView.widthAnchor.constraint(greaterThanOrEqualToConstant: 100),
containerView.heightAnchor.constraint(greaterThanOrEqualToConstant: 34),
containerView.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor, constant: 64)
])
}
private func setupContentLabelConstraints() {
NSLayoutConstraint.activate([
contentLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 4),
contentLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 8),
contentLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -8),
])
}
private func setupStackViewConstraints() {
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: contentLabel.bottomAnchor, constant: 0),
stackView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -6),
stackView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -4),
stackView.leadingAnchor.constraint(greaterThanOrEqualTo: containerView.leadingAnchor, constant: 6),
stackView.heightAnchor.constraint(equalToConstant: 20),
])
}
private func layoutUI() {
addSubviews()
setupContainerViewConstraints()
setupContentLabelConstraints()
setupStackViewConstraints()
}
func configure(model: MessageViewModel) {
containerView.transform = CGAffineTransform(scaleX: 1, y: -1)
contentLabel.text = model.content
timeLabel.text = model.time
checkMarkImageView.isHidden = !model.isSent
}
}
The output of UICollectionView is as the following
https://www.youtube.com/watch?v=RajfZk5lGCQ
The output of the UITableview is as the following
https://www.youtube.com/watch?v=6ayG7WhYKXo
It might not be really clear but if you look closely you can see the cells are "briefly" animating and with longer messages it looks kind of "sliding animation" which does not leak appealing at all.

Cells won't show in UICollectionView

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()
}

Swift CollectionView is selecting multiple cell when is set to false

Im currently have a horizontal collection view. I currently have multiple selection enabled set to off. For some reason when I select the first item the item toward the end of the collectionView also become selected. Thank you for the hep.
protocol customDatePickerDelegate:class {
func dateFromCustomDatePicker(date:Date)
}
class datePickerCollectionViewCell:UICollectionViewCell {
var labelNumber:UILabel = UILabel()
var minuteLabel:UILabel = {
let v = UILabel()
v.text = "Minutes"
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = .white
minuteLabel.labelFormCustom(labelType: .dateForm)
labelNumber.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(labelNumber)
labelNumber.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: 0).isActive = true
labelNumber.centerXAnchor.constraint(equalTo: self.centerXAnchor, constant: 0).isActive = true
minuteLabel.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(minuteLabel)
minuteLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor, constant: 0).isActive = true
minuteLabel.topAnchor.constraint(equalTo: labelNumber.bottomAnchor, constant: 0).isActive = true
self.layer.cornerRadius = 6
self.layer.borderWidth = 0.5
self.layer.borderColor = UIColor(red:205.0/255.0, green:205.0/255.0, blue:205.0/255.0, alpha: 1.0).cgColor
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class NewRquestDatePickerCollectionViewController: UIViewController,UICollectionViewDelegate {
var collectionView:UICollectionView!
override func viewDidLoad() {
setupCollectionView()
}
lazy var centerView:UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.heightAnchor.constraint(equalToConstant: 300).isActive = true
v.backgroundColor = .white
v.addBorderCustom(.bottom)
return v
}()
lazy var durationLabel:UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.textAlignment = .center
v.font = v.font.withSize(17)
return v
}()
lazy var topActionBar:UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.heightAnchor.constraint(equalToConstant: 60).isActive = true
v.backgroundColor = UIColor(hex: "F9F9F9")
v.addBorderCustom(.top)
v.addBorderCustom(.bottom)
return v
}()
lazy var doneButton:UIButton! = {
let v = UIButton(type:.system)
v.translatesAutoresizingMaskIntoConstraints = false
v.setTitle("Done", for: .normal)
v.addTarget(self, action: #selector(doneButtonPressed), for: .touchUpInside)
return v
}()
lazy var cancelButton:UIButton = {
let v = UIButton(type:.system)
v.translatesAutoresizingMaskIntoConstraints = false
v.setTitle("Cancel", for: .normal)
v.addTarget(self, action: #selector(dismissCurrentView), for: .touchUpInside)
return v
}()
lazy var dayLabel:UILabel = {
let v = UILabel()
// v.text = "Monday"
return v
}()
lazy var titleView:UILabel = {
let v = UILabel()
v.text = "Select a deadline"
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
var selectedIndexPath: NSIndexPath?
// var selectedTime:Int!
var timer = Timer()
var timeSeleted:TimeInterval = 10.0
let timeArray:[TimeInterval] = [10,20,30,45,60,140,400,900,1200]
var delegate:customDatePickerDelegate!
var dateSelected:Date!
func setupCollectionView(){
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
//collection view setup
self.view.addSubview(centerView)
centerView.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 0).isActive = true
centerView.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: 0).isActive = true
centerView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
collectionView = UICollectionView(frame: view.frame, collectionViewLayout: layout)
collectionView.register(datePickerCollectionViewCell.self, forCellWithReuseIdentifier: "imageCell")
collectionView.delegate = self
collectionView.dataSource = self
centerView.addSubview(topActionBar)
topActionBar.heightAnchor.constraint(equalToConstant: 49).isActive = true
topActionBar.leftAnchor.constraint(equalTo: self.centerView.leftAnchor, constant: 0).isActive = true
topActionBar.rightAnchor.constraint(equalTo: self.centerView.rightAnchor, constant: 0).isActive = true
topActionBar.topAnchor.constraint(equalTo: self.centerView.topAnchor, constant: 0).isActive = true
topActionBar.addSubview(cancelButton)
topActionBar.addSubview(titleView)
cancelButton.leftAnchor.constraint(equalTo: topActionBar.leftAnchor, constant: 16).isActive = true
cancelButton.centerYAnchor.constraint(equalTo: topActionBar.centerYAnchor, constant: 0).isActive = true
titleView.centerXAnchor.constraint(equalTo: topActionBar.centerXAnchor, constant: 0).isActive = true
titleView.centerYAnchor.constraint(equalTo: topActionBar.centerYAnchor, constant: 0).isActive = true
centerView.addSubview(doneButton)
centerView.addBorderCustom(.bottom)
doneButton.centerXAnchor.constraint(equalTo: centerView.centerXAnchor, constant: 0).isActive = true
doneButton.bottomAnchor.constraint(equalTo: centerView.bottomAnchor, constant: -20).isActive = true
centerView.addBorderCustom(.bottom)
centerView.addSubview(collectionView)
collectionView.heightAnchor.constraint(equalToConstant: 100).isActive = true
collectionView.leftAnchor.constraint(equalTo: self.centerView.leftAnchor, constant: 0).isActive = true
collectionView.rightAnchor.constraint(equalTo: self.centerView.rightAnchor, constant: 0).isActive = true
collectionView.topAnchor.constraint(equalTo: self.topActionBar.bottomAnchor, constant: 0).isActive = true
dayLabel.translatesAutoresizingMaskIntoConstraints = false
centerView.addSubview(dayLabel)
dayLabel.centerXAnchor.constraint(equalTo: collectionView.centerXAnchor, constant: 0).isActive = true
dayLabel.topAnchor.constraint(equalTo: collectionView.bottomAnchor, constant: 20).isActive = true
centerView.addSubview(durationLabel)
durationLabel.leftAnchor.constraint(equalTo: self.centerView.leftAnchor, constant: 0).isActive = true
durationLabel.rightAnchor.constraint(equalTo: self.centerView.rightAnchor, constant: 0).isActive = true
durationLabel.topAnchor.constraint(equalTo: self.collectionView.bottomAnchor, constant: 0).isActive = true
durationLabel.bottomAnchor.constraint(equalTo: self.doneButton.topAnchor, constant: 20).isActive = true
timerStart()
collectionView.showsHorizontalScrollIndicator = false
collectionView.showsVerticalScrollIndicator = false
collectionView.contentInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)
collectionView.isPagingEnabled = true
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.backgroundColor = UIColor(hex:"EFF3F4")
centerView.addBorderCustom(.bottom)
collectionView.allowsMultipleSelection = false
}
}
extension NewRquestDatePickerCollectionViewController {
func dismissCurrentView(){
self.dismiss(animated: true , completion: nil)
}
func doneButtonPressed(){
delegate.dateFromCustomDatePicker(date: dateSelected)
dismissCurrentView()
}
func tappedDeadlineSelection(){
}
func timerStart(){
if #available(iOS 10.0, *) {
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { (timer) in
let date = Date()
let adjustedDate = date.addingTimeInterval(self.timeSeleted * 60.0)
self.dateSelected = adjustedDate
self.durationLabel.text = String(describing: adjustedDate.string(withFormat: "MM/dd/yy, h:mm a"))
self.dayLabel.text = String(describing: adjustedDate.string(withFormat: "EEEE"))
})
} else {
// Fallback on earlier versions
}
}
}
extension NewRquestDatePickerCollectionViewController:UICollectionViewDataSource,UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return timeArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "imageCell", for: indexPath) as! datePickerCollectionViewCell
cell.awakeFromNib()
return cell
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
let cellInfo = cell as! datePickerCollectionViewCell
//converTime
cellInfo.labelNumber.text = String(self.timeArray[indexPath.row])
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! datePickerCollectionViewCell
let timeAdded = self.timeArray[indexPath.row]
cell.backgroundColor = UIColor(hex: "F2784B")
// let timeAdded = self.timeArray[indexPath.row]
cell.labelNumber.textColor = .white
cell.minuteLabel.textColor = .white
cell.layer.borderColor = UIColor.white.cgColor
timeSeleted = timeAdded
collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizontally)
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! datePickerCollectionViewCell
cell.backgroundColor = .white
cell.labelNumber.textColor = .black
cell.minuteLabel.textColor = UIColor().RquestCustomColors(.fontGray)
// collectionView.performBatchUpdates(nil, completion: nil)
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 64, height: 64)
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 30.0
}
func collectionView(_ collectionView: UICollectionView, layout
collectionViewLayout: UICollectionViewLayout,
minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 30.0
}
}
Because you are reusing existed cell, you should keep track selected item index and update cell state as below:
var selectedIndex: IndexPath?
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! datePickerCollectionViewCell
let timeAdded = self.timeArray[indexPath.row]
cell.backgroundColor = UIColor(hex: "F2784B")
// let timeAdded = self.timeArray[indexPath.row]
cell.labelNumber.textColor = .white
cell.minuteLabel.textColor = .white
cell.layer.borderColor = UIColor.white.cgColor
timeSeleted = timeAdded
collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizontally)
// add the following codes
let lastSelectedIndex = self.selectedIndex
self.selectedIndex = indexPath
// unselect last selected index
collectionView.reloadItems(at: [lastSelectedIndex!])
// reload new selected index
collectionView.reloadItems(at: [self.selectedIndex!])
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
let cellInfo = cell as! datePickerCollectionViewCell
//converTime
cellInfo.labelNumber.text = String(self.timeArray[indexPath.row])
// add this line of code
cell.isSelected = (self.selectedIndex == indexPath)
}
You should make use of the "isSelected" method for the collection view cell. This way you will not have not save any selected indexpath.
So I recommend using this method:
Firstly,
collectionView.allowsMultipleSelection = false
on the UICollectionViewCell you can override the isSelected method
didSet {
if isSelected {
// Change UI for selected state
radioButton.setImage(#imageLiteral(resourceName: "greenTick"), for: .normal)
} else {
// Chage UI for unselected state
radioButton.setImage(#imageLiteral(resourceName: "radioInactive"), for: .normal)
}
}
Finally when you need to find out the indexpath for the selected item.
guard let selectedIndex = self.collectionView.indexPathsForSelectedItems?.first else { return }

Resources