Section headers in UICollectionView, only first one appears - swift

I have created a class for a section header and am loading it into my UICollectionView. I am able to display the first header (albeit oddly, see below), however any following section headers are blank. The size is being referenced, the content (background color, label) won't appear though.
And then also...the one section header that does show, it is displayed with an indentation of approx. 150px for no apparent reason. Are headers center aligned by default? If so, how would I left align those?
My Section Header class:
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionView.elementKindSectionHeader:
let section = indexPath.section
switch section {
case 0:
let tagsHeader = searchCollectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "TagsHeaderView", for: indexPath) as! SectionHeaderView
tagsHeader.headerString = "Recent Tags"
tagsHeader.backgroundColor = .green
return tagsHeader
default:
let tagsHeader = searchCollectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "TypeHeaderView", for: indexPath) as! SectionHeaderView
tagsHeader.headerString = "Type"
tagsHeader.backgroundColor = .blue
return tagsHeader
}
default:
return UICollectionReusableView()
}
}
In my UICollectionView
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionView.elementKindSectionHeader:
let section = indexPath.section
switch section {
case 0:
let tagsHeader = searchCollectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "TagsHeaderView", for: indexPath) as! SectionHeaderView
tagsHeader.headerString = "Recent Tags"
tagsHeader.backgroundColor = .green
return tagsHeader
default:
let tagsHeader = searchCollectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "TypesHeaderView", for: indexPath) as! SectionHeaderView
tagsHeader.headerString = "Type"
tagsHeader.backgroundColor = .blue
return tagsHeader
}
default:
return UICollectionReusableView()
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
if section == 0 {
return CGSize(width: view.frame.width, height: 18 + 22 + 6)
} else {
return CGSize(width: view.frame.width, height: 100)
}
}
This is how I instantiate my UICollectionView
let searchCollectionView: UICollectionView = {
let layout = LeftAlignedCollectionViewFlowLayout()
layout.minimumInteritemSpacing = 6
layout.minimumLineSpacing = 6
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.isScrollEnabled = false
cv.register(TokenCell.self, forCellWithReuseIdentifier: "TokenCell")
cv.register(SectionHeaderView.self, forSupplementaryViewOfKind:UICollectionView.elementKindSectionHeader, withReuseIdentifier: "TagsHeaderView")
cv.register(SectionHeaderView.self, forSupplementaryViewOfKind:UICollectionView.elementKindSectionHeader, withReuseIdentifier: "TypesHeaderView")
cv.backgroundColor = .white
cv.showsVerticalScrollIndicator = false
cv.alwaysBounceVertical = false
cv.translatesAutoresizingMaskIntoConstraints = false
return cv
}()
My CollectionViewFlowLayout
class LeftAlignedCollectionViewFlowLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let attributes = super.layoutAttributesForElements(in: rect)
var leftMargin = sectionInset.left
var maxY: CGFloat = -1.0
attributes?.forEach { layoutAttribute in
if layoutAttribute.frame.origin.y >= maxY {
leftMargin = sectionInset.left
}
layoutAttribute.frame.origin.x = leftMargin
leftMargin += layoutAttribute.frame.width + minimumInteritemSpacing
maxY = max(layoutAttribute.frame.maxY , maxY)
}
return attributes
}
}
Here is a screenshot of the current result. You can see that the first header appears, however indented. The second header has its space, but none of its content appears.

I got the same problem when making my tag view.
The problem is in your CollectionViewFlowLayout.
let attributes = super.layoutAttributesForElements(in: rect)
This line is getting all the attributes including cells, supplementaryviews and decorationviews, which means you are changing attributes for header and footer too.
We can change the code to:
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let attributes = super.layoutAttributesForElements(in: rect)
var leftMargin = sectionInset.left
var maxY: CGFloat = -1.0
attributes?.forEach { layoutAttribute in
//check if the layoutAttribute is for cell
if layoutAttribute.representedElementCategory == .cell {
if layoutAttribute.frame.origin.y >= maxY {
leftMargin = sectionInset.left
}
layoutAttribute.frame.origin.x = leftMargin
leftMargin += layoutAttribute.frame.width + minimumInteritemSpacing
maxY = max(layoutAttribute.frame.maxY , maxY)
}
}
return attributes
}
In order to apply the attribute to just cell. Then your header and footer will appear properly.

Related

Set custom space for UICollectionViewCell for every rows

I build ViewController with UiCollectionView, and I created my custom View to display in every cell.
This is the code on my controller to display, resize the cell.
I need to have 3 cell for every row in UiCollectionView
class HomeViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource , UICollectionViewDelegateFlowLayout{
#IBOutlet weak var collectionView: UICollectionView!
var listaCategorie = [CategoryModel]()
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.listaCategorie.count
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let noOfCellsInRow = 3
let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout
let totalSpace = flowLayout.sectionInset.left
+ flowLayout.sectionInset.right
+ 30
+ (flowLayout.minimumInteritemSpacing * CGFloat(noOfCellsInRow - 1))
let size = Int((collectionView.bounds.width - totalSpace) / CGFloat(noOfCellsInRow))
return CGSize(width: 100, height: 130)
}
// UICollectionViewDelegateFlowLayout method
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout,
insetForSectionAtIndex section: Int) -> UIEdgeInsets {
let cellWidthPadding = collectionView.frame.size.width / 30
let cellHeightPadding = collectionView.frame.size.height / 4
return UIEdgeInsets(top: cellHeightPadding,left: cellWidthPadding, bottom: cellHeightPadding,right: cellWidthPadding)
}
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
var category = self.listaCategorie[indexPath.row];
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cella", for: indexPath) as! CustomCellViewsCategories
var puntoLuce = self.listaCategorie[indexPath.row];
cell.labelCategoryName.text = puntoLuce.description
//cell.image.image = UIImage(named: "light-bulb-2.png");
cell.backgroundColor = getUIColorFromRGBThreeIntegers(red: 63,green: 162,blue: 217);
cell.layer.cornerRadius = 6
cell.layer.masksToBounds = false;
cell.layer.shadowColor = UIColor.black.cgColor
cell.layer.shadowOffset = CGSize(width: 0, height: 0)
cell.layer.shadowOpacity = 0.5
//RECUPERO LA DIMENSIONE
let noOfCellsInRow = 4
//FINE RECUPERO DIMENSIONE
if(puntoLuce.imageUrl != ""){
let imageUrl:NSURL = NSURL(string: puntoLuce.imageUrl!)!
DispatchQueue.global(qos: .userInitiated).async {
let imageData:NSData = NSData(contentsOf: imageUrl as URL)!
DispatchQueue.main.async {
let image = UIImage(data: imageData as Data)
cell.imageCategory.image = image
}
}
}
return cell
}
func getUIColorFromRGBThreeIntegers(red: Int, green: Int, blue: Int) -> UIColor {
return UIColor(red: CGFloat(Float(red) / 255.0),
green: CGFloat(Float(green) / 255.0),
blue: CGFloat(Float(blue) / 255.0),
alpha: CGFloat(1.0))
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.title = "ARRIVA ARRIVA"
//on click su label temp
let tap = UITapGestureRecognizer(target: self, action: #selector(HomeViewController.tapFunction))
getCategoryList()
collectionView.delegate = self // Unless you have already defined the delegate in IB
collectionView.dataSource = self // Unless you have already defined the dataSource in IB
self.collectionView.frame = self.collectionView.frame.inset(by: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0))
}
#objc func tapFunction() {
// handle label tap here
print("click");
}
func getCategoryList(){
var params = [
"" : ""
]
let postUrl = APIRequest(endPoint: "category_list")
postUrl.sendRequest(parameters: params as! [String : String]) {
responseObject, error in
let user = CategoryModel(id: "0",
description: "Tutti",
imageUrl: "")
self.listaCategorie.append(user)
guard let responseObject = responseObject, error == nil else {
print(error ?? "Unknown error")
return
}
do{
let messageData = try JSONDecoder().decode(ResponseCategoryModel.self, from: responseObject)
var array = messageData.result
for categoryModel in array {
let user = CategoryModel(id: "",
description: categoryModel.categoryName,
imageUrl: categoryModel.image)
self.listaCategorie.append(user)
}
print(array.count);
DispatchQueue.main.async { // Correct
self.collectionView.reloadData()
}
}catch{
print("errore durante la decodifica dei dati")
}
}
}
But this is the result:
As you can see from the photo there is too space from the 3 cells. There is a way to set minus space by cells?
EDIT
I try to use the code on first response. THis is the result
UICollectionViewCompositionalLayout will give you a layout that automatically adjusts to the collection view size. Remove all code you have relating to the flow layout and create a compositional layout in viewDidLoad:
// Cell will be the full height of the enclosing group
let cellHeight = NSCollectionLayoutDimension.fractionalHeight(1)
// Cell will be 1/3 width of the enclosing group
let cellWidth = NSCollectionLayoutDimension.fractionalWidth(0.333)
// The size of the cell
let size = NSCollectionLayoutSize(widthDimension: cellWidth, heightDimension: cellHeight)
// This item represents a single cell
let item = NSCollectionLayoutItem(layoutSize: size)
// The cell will be inset by these distances within the item
item.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
// The group will be a fixed height
let groupHeight = NSCollectionLayoutDimension.absolute(130)
// The group will occupy the full available width
let groupWidth = NSCollectionLayoutDimension.fractionalWidth(1)
// The group will repeat to hold as many of the cells as it can in a horizontal row before wrapping
let group = NSCollectionLayoutGroup.horizontal(layoutSize: NSCollectionLayoutSize(widthDimension: groupWidth, heightDimension: groupHeight), subitems: [item])
// The actual section, which consists of a single group
let section = NSCollectionLayoutSection(group: group)
// The insets of the group from the edge of the collection view
section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
// Create and assign the layout
let layout = UICollectionViewCompositionalLayout(section: section)
collectionView.collectionViewLayout = layout
I've tried to break it up into chunks so it makes sense, these layouts can take some time to wrap your head around.
It gives you the following portrait layout:
And in landscape:
If you want a fixed cell size, then use .absoluteWidth for the cell width, and add an interItemSpacing of .flexible to the group.
Using UICollectionViewFlowLayout you can achieve a very similar result with less code than you have in your question. With a plain project, the only collection view related code I had was this in viewDidLoad():
(collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.sectionInset = .init(top: 10, left: 10, bottom: 10, right: 10)
Then this single flow layout delegate method:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
var width = (collectionView.bounds.width - 20) / 3
width -= 10
return CGSize(width: width, height: 130)
}
This gives you three columns per row in portrait or landscape.
Add the UICollectionViewDelegateFlowLayout delegate and add these methods and update your values according to your requirement like:-
let edge : CGFloat = 10.0
let spacing : CGFloat = 10.0
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let noOfColumn = 3
let collectionviewWidth = collectionView.frame.width
let bothEdge = CGFloat(edge + edge) // left + right
let excludingEdge = collectionviewWidth - bothEdge
let cellWidthExcludingSpaces = excludingEdge - ((noOfColumn-1) * spacing)
let finalCellWidth = cellWidthExcludingSpaces / noOfColumn
let height = finalCellWidth
return CGSize(width: finalCellWidth, height: height)
}

How to achieve a dynamic CollectionView Cell Height (Size for item at)?

I have decided start a project with no storyboard for the first time and at the moment I am stuck trying to figuring out how to achieve a proper dynamic cell in my CollectionViewController. Reading some of the solutions here in Stackoverflow I got the point in using a layout.estimatedItemSize but it somehow stops the bouncing effect from the collection view and also in my second cell which is a horizontal scroll view will not work after this implementation.
Here is my code(UICollectionViewController):
class InfoEmpaVC: UICollectionViewController, UICollectionViewDelegateFlowLayout {
fileprivate let cell1 = "cell1"
fileprivate let cell2 = "cell2"
fileprivate let cellID = "cellID"
fileprivate let headerID = "headerID"
fileprivate let padding: CGFloat = 10
//
//
//GET THE DATA FROM:
var empanada: Empanadas!
struct Cells {
static let empanadaStepsCell = "EmpanadaStepsCell"
}
override func viewDidLoad() {
super.viewDidLoad()
setupCollectionViewLayout()
setupCollectionView()
}
//CHANGE COLOR OF STATUS BAR
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
fileprivate func setupCollectionView() {
collectionView.backgroundColor = UIColor(named: "ColorBackground")
collectionView.contentInsetAdjustmentBehavior = .never
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(InfoHeaderVC.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: headerID)
//FirstCELL
collectionView.register(EmpaIngredientsListCell.self, forCellWithReuseIdentifier: cell1)
//SecondCELL
collectionView.register(EmpaStepsCell.self, forCellWithReuseIdentifier: cellID)
}
fileprivate func setupCollectionViewLayout() {
if let layout = collectionViewLayout as? UICollectionViewFlowLayout {
layout.sectionInset = .init(top: padding, left: padding, bottom: padding, right: padding)
layout.estimatedItemSize = CGSize(width: view.frame.width, height: 50)
}
}
var headerView: InfoHeaderVC!
//HEADER COLLECTION VIEW
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerID, for: indexPath) as? InfoHeaderVC
headerView.empaImageView.image = UIImage(named: empanada.image)
headerView.empaTitleLabel.text = empanada.name
headerView.empaDescriptionLabel.text = empanada.info
headerView.buttonX.addTarget(self, action: #selector(dismissVC), for: .touchUpInside)
headerView.buttonAddFavorite.addTarget(self, action: #selector(addButtonTapped), for: .touchUpInside)
return headerView!
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return .init(width: view.frame.width, height: 350)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.item == 0 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cell1, for: indexPath)
guard let cellOne = cell as? EmpaIngredientsListCell else {
fatalError("Wrong cell type for section 0. Expected CellTypeOne")
}
//INGREDIENT LIST
cellOne.empaIngredientList.ingredientList.append(contentsOf: empanada.ingredients)
cellOne.empaIngredientList.configure()
return cellOne
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath) as! EmpaStepsCell
cell.pasos.append(contentsOf: empanada.pasos)
cell.titleHeaderLabel.text = "Step by Step"
cell.configure()
print (cell.pasos.count)
return cell
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if indexPath.item == 0 {
return .init(width: view.frame.width - 2 * padding, height: 300)
} else {
return .init(width: view.frame.width - 2 * padding, height: 300)
}
}
//OBJC FUNC
#objc func dismissVC() {
dismiss(animated: true)
}
//SAVE DATA
#objc func addButtonTapped() {
configureSaveToFavorites(empanada: empanada!)
}
}
Cell 1:
import UIKit
import SnapKit
class EmpaIngredientsListCell: UICollectionViewCell {
let empaIngredientList = EmpaIngredientsContainerView()
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
print(intrinsicContentSize)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
setNeedsLayout()
layoutIfNeeded()
let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
var frame = layoutAttributes.frame
frame.size.height = ceil(size.height)
layoutAttributes.frame = frame
return layoutAttributes
}
func setupUI() {
//contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(empaIngredientList)
empaIngredientList.snp.makeConstraints { (make) in
make.top.bottom.left.right.equalTo(self.contentView)
make.edges.equalTo(self.safeAreaLayoutGuide)
}
}
}

having issues with cached table viewcells

I have the following code
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCell(withIdentifier: "book", for: indexPath) as! BookTableViewCell
cell.cellIndex = indexPath
cell.dataSource = self
cell.delegate = self
cell.backgroundColor = UIColor.white
var books = [Book]();
let count = self.enterpriseBooks.count;
if count > 0 && indexPath.section < self.enterpriseBooks_2.count {
books = self.enterpriseBooks_2[indexPath.section]!;
}
if (indexPath.section == (count)) {
books = nytbooks;
} else if (indexPath.section == (count + 1)) {
books = trendingbooks;
} else if (indexPath.section == (count + 2)) {
books = newbooks
}
if (books.count > 0) {
if (cell.collectionView === nil) {
cell.addCollectionView();
cell.collectionView.tag = 124;
cell.collectionView.reloadData()
}
}
return cell
}
// BookTableViewCell
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCell", for: indexPath as IndexPath) as! BookCollectionViewCell
self.dataSource?.getData(cell: self)
let books = self.books
let book = books?[indexPath.row]
if book != nil {
cell.label.text = book?.title
cell.imageView.sd_setImage(with: URL(string: book?.image as! String), placeholderImage: nil)
}
return cell
}
func addCollectionView () {
let flowLayout = UICollectionViewFlowLayout()
flowLayout.scrollDirection = .horizontal
flowLayout.itemSize = CGSize(width: 100, height: self.frame.height)
//You can also provide estimated Height and Width
flowLayout.estimatedItemSize = CGSize(width: 100, height: self.frame.height)
//For Setting the Spacing between cells
flowLayout.minimumInteritemSpacing = 25
flowLayout.minimumLineSpacing = 20
let cellReuseIdentifier = "collectionCell"
self.collectionView = UICollectionView(frame: CGRect(x: 10,
y: 0,
width: self.frame.width,
height: self.frame.height),
collectionViewLayout: flowLayout)
self.collectionView.showsHorizontalScrollIndicator = false
self.collectionView.translatesAutoresizingMaskIntoConstraints = false
self.collectionView.backgroundColor = .clear
self.collectionView.delegate = self
self.collectionView.dataSource = self
self.collectionView.register(BookCollectionViewCell.self, forCellWithReuseIdentifier: cellReuseIdentifier)
self.backgroundColor = UIColor.white
self.addSubview(collectionView)
}
The issue is I'm running into caching issues where content in a table view cell or collection view cell are overlapping with each other. Can anyone please help?
The above content shows images horizontally aligned into three rows using collection views for each.

Swift 3 - CollectionView data source did not return a valid cell

I am using this code: https://www.youtube.com/watch?v=bNtsekO51iQ , but when I implement my data and use collectionView.reloadData() it crashes with error code *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'the collection view's data source did not return a valid cell from -collectionView:cellForItemAtIndexPath: for index path <NSIndexPath: 0xc000000000000016> {length = 2, path = 0 - 0}'
class ChatLogController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
var userId:Int?
var position:Int = 0
var otherAvatar:UIImage = UIImage(named: "defaultAvatar")!
var otherName:String = ""
var otherSex:String = ""
var otherBanned:Int = 0
var otherBlocked:Int = 0
var messagesDates:[String] = []
var messagesText:[String] = []
var messagesIds:[String] = []
var messagesPics:[UIImage?] = []
var messagesSeen:[Int] = []
var messagesWhoSendIt:[Int] = []
private let cellId = "cellId"
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.register(ChatLogMessageCell.self, forCellWithReuseIdentifier: cellId)
collectionView!.isPrefetchingEnabled = false
loadChatsFor(position: position)
} override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return messagesIds.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath as IndexPath) as! ChatLogMessageCell
cell.messageTextView.text = messagesText[indexPath.row]
cell.profileImageView.image = UIImage(named: "defaultAvatar")!
let size = CGSize(width:250,height:1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let estimatedFrame = NSString(string: messagesText[indexPath.row]).boundingRect(with: size, options: options, attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 18)], context: nil)
cell.messageTextView.frame = CGRect(x:48 + 8, y:0, width:estimatedFrame.width + 16, height:estimatedFrame.height + 20)
cell.textBubbleView.frame = CGRect(x:48, y:0, width:estimatedFrame.width + 16 + 8, height:estimatedFrame.height + 20)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let size = CGSize(width:250,height:1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let estimatedFrame = NSString(string: messagesText[indexPath.row]).boundingRect(with: size, options: options, attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 18)], context: nil)
return CGSize(width:view.frame.width, height:estimatedFrame.height + 20)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsetsMake(8, 0, 0, 0)
}
}
class ChatLogMessageCell: BaseCell {
let messageTextView: UITextView = {
let textView = UITextView()
textView.font = UIFont.systemFont(ofSize: 18)
textView.text = "Sample message"
textView.backgroundColor = UIColor.clear
return textView
}()
let textBubbleView: UIView = {
let view = UIView()
view.backgroundColor = UIColor(white: 0.95, alpha: 1)
view.layer.cornerRadius = 15
view.layer.masksToBounds = true
return view
}()
let profileImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.layer.cornerRadius = 15
imageView.layer.masksToBounds = true
return imageView
}()
override func setupViews() {
super.setupViews()
addSubview(textBubbleView)
addSubview(messageTextView)
addSubview(profileImageView)
addConstraintsWithFormat(format:"H:|-8-[v0(30)]", views: profileImageView)
addConstraintsWithFormat(format:"V:[v0(30)]|", views: profileImageView)
profileImageView.backgroundColor = UIColor.red
}
}
extension UIView {
func addConstraintsWithFormat(format: String, views: UIView...) {
var viewsDictionary = [String: UIView]()
for (index, view) in views.enumerated() {
let key = "v\(index)"
viewsDictionary[key] = view
view.translatesAutoresizingMaskIntoConstraints = false
}
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary))
}
}
class BaseCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
}
}
In loadChatsFor I get the array data from web and I use collectionView.reloadData(), but when this function is performed it crashes my app. I've searched for answer, but unsuccessfully. I've added IOS 10 function collectionView!.isPrefetchingEnabled = false in view did load from this answer UICollectionView exception in UICollectionViewLayoutAttributes from iOS7, but also not working. Even the method collectionViewLayout invalidateLayout before reloadData and after reloadData doesn't stop the crash. So what else I can do to make it work ?
I am coming in this CollectionViewController from UITableViewCell
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let layout = UICollectionViewFlowLayout()
let controller = ChatLogController(collectionViewLayout: layout)
controller.userId = chatUserIds[indexPath.row]
navigationController?.pushViewController(controller, animated: true)
}
I've added UICollectionViewDelegateFlowLayout in the class of the tableview
You are not implementing correct cellForItemAt method of UICollectionViewDataSource. Signature of this method is changed in Swift 3 like this.
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath as IndexPath) as! ChatLogMessageCell
cell.messageTextView.text = messagesText[indexPath.row]
cell.profileImageView.image = UIImage(named: "defaultAvatar")!
let size = CGSize(width:250,height:1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let estimatedFrame = NSString(string: messagesText[indexPath.row]).boundingRect(with: size, options: options, attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 18)], context: nil)
cell.messageTextView.frame = CGRect(x:48 + 8, y:0, width:estimatedFrame.width + 16, height:estimatedFrame.height + 20)
cell.textBubbleView.frame = CGRect(x:48, y:0, width:estimatedFrame.width + 16 + 8, height:estimatedFrame.height + 20)
return cell
}

prevent the uicollectionview cell form moving to the right when there is space -- SWIFT

my problem is that the uicollectionview cell is moving to left when the next cell jumps to the second line and there is space in the first line (space which didnt fit to contain the next cell), how could i prevent the cell from moving to the right, even when there is still space, i need some thing like setMaximumSpace between cells (which doesnt exist) ?
here is my code :
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = mycollectionView.dequeueReusableCellWithReuseIdentifier("CharakterCell", forIndexPath: indexPath) as CharakterCollectionViewCell
for (var i=0 ; i<4 ; i++)
{
if (collectionCellIndex[i] == nil)
{
collectionCellIndex[i] = indexPath
println("\(collectionCellIndex[i])")
println(i)
break
}
}
return cell
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout:UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
var size = CGSize(width: 0, height: 0)
var label = UILabel()
if (selectedCharInCollectionNames[indexPath.row] == "")
{
size = CGSize(width: 40, height: 30)
}
else
{
label.text = selectedCharInCollectionNames[indexPath.row]
label.sizeToFit()
var width = label.frame.width
size = CGSize(width: (width+25), height: 30)
}
label.sizeToFit()
return size
}
private let sectionInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
func collectionView(collectionView: UICollectionView!,
layout collectionViewLayout: UICollectionViewLayout!,
insetForSectionAtIndex section: Int) -> UIEdgeInsets {
return sectionInsets
}
#Kevin R answer here! solved my question
class AlignLeftFlowLayout: UICollectionViewFlowLayout {
var maximumCellSpacing = CGFloat(9.0)
override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? {
let attributesToReturn = super.layoutAttributesForElementsInRect(rect) as? [UICollectionViewLayoutAttributes]
for attributes in attributesToReturn ?? [] {
if attributes.representedElementKind == nil {
attributes.frame = self.layoutAttributesForItemAtIndexPath(attributes.indexPath).frame
}
}
return attributesToReturn
}
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
let curAttributes = super.layoutAttributesForItemAtIndexPath(indexPath)
let sectionInset = (self.collectionView?.collectionViewLayout as UICollectionViewFlowLayout).sectionInset
if indexPath.item == 0 {
let f = curAttributes.frame
curAttributes.frame = CGRectMake(sectionInset.left, f.origin.y, f.size.width, f.size.height)
return curAttributes
}
let prevIndexPath = NSIndexPath(forItem: indexPath.item-1, inSection: indexPath.section)
let prevFrame = self.layoutAttributesForItemAtIndexPath(prevIndexPath).frame
let prevFrameRightPoint = prevFrame.origin.x + prevFrame.size.width + maximumCellSpacing
let curFrame = curAttributes.frame
let stretchedCurFrame = CGRectMake(0, curFrame.origin.y, self.collectionView!.frame.size.width, curFrame.size.height)
if CGRectIntersectsRect(prevFrame, stretchedCurFrame) {
curAttributes.frame = CGRectMake(prevFrameRightPoint, curFrame.origin.y, curFrame.size.width, curFrame.size.height)
} else {
curAttributes.frame = CGRectMake(sectionInset.left, curFrame.origin.y, curFrame.size.width, curFrame.size.height)
}
return curAttributes
}
}