Collection view cell content is layering - ios

My button labels are stacked/regenerated when I scroll. So the first label might display Alabel with Elabel on top of it after has left the view and re-entered. I'm simply trying to create a single row of scrollable buttons (or labels for that matter). The collection view and cell were created via Storyboard. The code generates the correct number of cells in the CV, but the labels become layered when scrolled (horizontal).
let buttonLabels = ["Alabel", "Blabel", "Clabel", "Dlabel", "Elabel", "Flabel", "Glabel", "Hlabel", "Ilabel", "Jlabel", "Klabel", "Llabel", "Mlabel"]
#IBOutlet weak var btnCollVw: UICollectionView!
//loadColFlowLayout() is called from ViewDidLoad()
func loadColFlowLayout() {
let btnLayout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
btnLayout.scrollDirection = UICollectionViewScrollDirection.Horizontal
btnLayout.sectionInset = UIEdgeInsets(top: 0, left: 1, bottom: 0, right: 1)
btnLayout.itemSize = CGSize(width: 63, height: 30)
btnCollVw.collectionViewLayout = btnLayout
btnCollVw!.backgroundColor = UIColor.whiteColor()
}
func numberOfSections() -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return buttonLabels.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
var cell: UICollectionViewCell = self.iconCollVw.dequeueReusableCellWithReuseIdentifier("swIconsCell", forIndexPath: indexPath) as UICollectionViewCell
var makeButton = UIButton(frame: CGRectMake(0, 0, 63, 29))
makeButton.setTitle(buttonLabels[indexPath.item], forState: .Normal)
makeButton.setTitleColor(UIColor.blackColor(), forState: .Normal)
cell.addSubview(makeButton)
// or using cell.contentView.addSubview(makeButton)
return cell
}
}

the problem in cell reusing, each time when you dequeue cell from collection view it can already has button, look at my a bit improved version when I check for button with known tag on cell:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
var cell: UICollectionViewCell = collectionView.dequeueReusableCellWithReuseIdentifier("swIconsCell", forIndexPath: indexPath) as UICollectionViewCell
if cell.viewWithTag(1234) == nil {
var makeButton = UIButton(frame: CGRectMake(0, 0, 63, 29))
makeButton.setTitle(buttonLabels[indexPath.item], forState: .Normal)
makeButton.setTitleColor(UIColor.blackColor(), forState: .Normal)
makeButton.tag = 1234;
cell.addSubview(makeButton)
// or using cell.contentView.addSubview(makeButton)
}
return cell
}
you can do the same in another way for example create subclass of UICollectionViewCell

Related

Positioning elements in UITableViewCell

I have class for a cell
import UIKit
class LinkCellView: UITableViewCell {
#IBOutlet weak var cellTitleLabel: UILabel!
#IBOutlet weak var tagsListView: UIView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
I fill the tagsListView in cellForRowAt
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "linkCell", for: indexPath) as! LinkCellView
let object = links[indexPath.row]
cell.cellTitleLabel!.text = object.description
let tag = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 21))
tag.text = "Frkul"
cell.tagsListView.addSubview(tag)
return cell
}
Unfortunately when I then run the app in simulator, list of tags is just barely visible there. My expectation is to see the whole tags list.
I am pretty new to iOS development, so it is possible I am missing some fundamental knowledge of designing iOS UI. If it is not possible to answer this question directly, pls point me to a tutorial / webinar taking newbies through this topic.
Xcode Version 10.0
iOS 12
iOS simulator Version 10.0
Static version of the App — https://gitlab.com/lipoqil/stackview-in-table-cell
Ok, for some reason, if I use UIStackView, instead of UIView, it displays almost as I wish.
It introduces one change in the code
cell.tagListView.addSubview(tag) → cell.tagListView.addArrangedSubview(tag)
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "linkCell", for: indexPath) as! LinkCellView
cell.tagListView.subviews.forEach { $0.removeFromSuperview() }
let object = links[indexPath.row]
cell.cellTitleLabel!.text = object.description
let tag = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 21))
tag.shadowColor = .blue
tag.text = object.tags
cell.tagListView.addArrangedSubview(tag)
return cell
}
I still need to solve, how to fit the tags there, how to make its look more tagish, but I believe that's beyond the original question.
if you have a collection of tags you need to implement a collection view into your tableView Cell, and if you just have 1 tag, just implement it in your xib ?
Here is my code.
func numberOfSections(in tagsCV: UICollectionView) -> Int {
return 1
}
func collectionView(_ tagsCV: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return videoObject?.tags?.count ?? 0
}
func collectionView(_ tagsCV: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize{
let fontAttributes = [NSAttributedStringKey.font: StylesBook.hashtag.value.textFont]
let size = (videoObject?.tags?[indexPath.row] ?? "" as String).size(withAttributes: fontAttributes)
return CGSize(width: size.width + CGFloat(20) , height: 50)
}
func collectionView(_ tagsCV: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = tagsCV.dequeueReusableCell(withReuseIdentifier: "TagCollectionViewCell", for: indexPath) as! TagCollectionViewCell
cell.tagBtn.setTitle(englishNumToPersian(text: videoObject?.tags?[indexPath.row] ?? "") , for: .normal)
cell.tagBtn.addTarget(self, action: #selector(tagTaped(sender:)), for: .touchDown)
cell.transform = CGAffineTransform(scaleX: -1, y: 1)
return cell
}
The key is that you need to set ur collection view's scroll direction horizontal. This way whether you have 1 or 1000 tag, they'll all be shown perfectly, and the button (or label) width will fit its content. I recommend you to use button, instead of label, and disable its user interaction to act like a label.

Why is only image in first cell updated while the rest of the cells in uicollectionview not updated. ios swift

I'm trying to get 3 icons on the top bar.
import UIKit
class SectionHeader: UICollectionReusableView {
private let telButtonCellId = "TelButtonCell"
private let searchBarCellId = "SearchBarCell"
private let notificationButtonCellId = "NotificationButtonCell"
// MARK: - Properties
#IBOutlet weak var collectionView: UICollectionView!
// MARK: - Initialization
override func awakeFromNib() {
super.awakeFromNib()
self.collectionView.register(UINib(nibName:notificationButtonCellId, bundle: nil), forCellWithReuseIdentifier: notificationButtonCellId)
self.collectionView.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.width*0.2) // this is need to set the size of the collection view
self.collectionView.delegate = self
self.collectionView.dataSource = self
}
extension SectionHeader : UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: notificationButtonCellId, for: indexPath) as! NotificationButtonCell
var image = UIImage(named: "globe")
cell.imageView.image=image?.addImagePaddingShift(x: 20 , y: 20,shiftX: 0, shiftY: 10)
cell.imageView.contentMode = .scaleAspectFit
cell.imageView.bounds=cell.bounds
cell.imageView.center = cell.center;
cell.backgroundColor = UIColor.lightGray
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let frameWidth = UIScreen.main.bounds.width
let frameHeight = UIScreen.main.bounds.width*0.2
return CGSize(width: frameWidth*0.15, height: frameHeight*1.0)
}
So only the first cell will have the globe displayed.
the rest of the 2 cells are empty even though i can change the background colour.
The SectionHeader is a nib
The imageView is subview of the cell, that needs to be with its size as I see. So it needs its frame (its position in relation to its superview coordinate system - the cell) to be set to the height and width of the cell with origin (0,0). The bounds of the cell provide these dimensions so the answer can be
cell.imageView.frame = cell.bounds
or better
cell.imageView.frame = CGRect(x: 0, y: 0, width: cell.frame.width, height: cell.frame.height)

how to reference a UICollectionView when its inside a UITableViewCell

I have a UITableView which has 2 sections. In section 1 is a static cell which has a horizontal collectionView inside it.
My question is how do I reference the collectionView in the Controller to reload the collectionView...
Here is my code:
TableView Controller
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "collectionCellID", for: indexPath) as! CollectionTableViewCell
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "tableCellID", for: indexPath) as! TableCell
return cell
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath.row == 0 {
if let cell = cell as? CollectionTableViewCell {
cell.collectionView.delegate = self
cell.collectionView.dataSource = self
cell.collectionView.contentInset = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8)
}
}
}
TableView Cell
class CollectionTableViewCell: UITableViewCell {
#IBOutlet weak var collectionView: UICollectionView!
}
CollectionView extension
extension MyController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
return cell
}
Data call in TableViewController
public func getData() {
ref.observe(.childAdded, with: { (snapshot) in
self.data.append(snapshot)
}
DispatchQueue.main.async {
//MARK: - collectionView.reloadData() <- not available
}
}
})
}
Call the table view's cellForRow(at:) to get a reference to the cell at section 0 row 0, cast that reference to a CollectionTableViewCell, and refer to its collectionView.
I had a tough time to configure the same issue (https://stackoverflow.com/a/45618501/3400991) . Here is few points regarding this :
Your Controller should conforms UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout
Create CollectionView cell and set its custom class into CollectionView cell .
Tableview dont have any idea about how much height its cell needed to render complete collectionview data inside tableview cell so you have to use this :
yourTableView.rowHeight = UITableViewAutomaticDimension
yourTableView.estimatedRowHeight = 90
Set Height of Tableview accordingly :
// since Collectionview explicitly telling its Parent to provide height
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
//Checking MIME Type
if condition for collectionview cell {
// set here
}
//Normal autolayout cell
else {
return UITableViewAutomaticDimension
}
}
Make Collectionview reference in Tableview Custom Cell class :
class customTableViewCell: UITableViewCell
{
#IBOutlet weak var collectionview: UICollectionView
}
Inside Tableview willDisplayCell :
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.collectionview.delegate = self
cell.collectionview.datasource = self
//Reload it also
cell.collectionview.reloadData()
}
So what I ended up doing was creating a Header View for the table and adding a collectionView to it.
func configureHeaderView() {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.sectionInset = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8)
collectionView = UICollectionView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 105), collectionViewLayout: layout)
collectionView.backgroundColor = UIColor(red: 248/255, green: 248/255, blue: 248/255, alpha: 1)
collectionView.isPagingEnabled = false
collectionView.isUserInteractionEnabled = true
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(UINib(nibName: "cell", bundle: nil), forCellWithReuseIdentifier: "cell")
collectionView.showsHorizontalScrollIndicator = false
tableView.tableHeaderView = collectionView
}
Then from anywhere I can now access:
DispatchQueue.main.async {
self?.collectionView.reloadData()
}

didSelectItemAt not working from SCLAlertView

I am using SCLAlertView to create custom alert view. My alert view contains one text field and collection view of coloured cells
Problem is that UICollectionView's didSelectItemAt method is not working. I think problem is because it is like subview. But I can't fix it.
I have one collection view at UIViewController and that method is working. Here's my code
var collectionViewAlert: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 1, left: 1, bottom: 1, right: 1)
layout.itemSize = CGSize(width: 25, height: 25)
collectionViewAlert = UICollectionView(frame: CGRect(x: 18, y: 10, width: 250, height: 25), collectionViewLayout: layout)
collectionViewAlert.dataSource = self
collectionViewAlert.delegate = self
collectionViewAlert.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "CollCell")
collectionViewAlert.backgroundColor = UIColor.white
}
#IBAction func addCategory(_ sender: Any) {
let alertView = SCLAlertView()
alertView.addTextField("Enter category name")
let subview = UIView(frame: CGRect(x:0,y:0,width:216,height:70))
subview.addSubview(self.collectionViewAlert)
alertView.customSubview = subview
alertView.showEdit("Choose color", subTitle: "This alert view has buttons")
}
let reuseIdentifier = "cell" // also enter this string as the cell identifier in the storyboard
var colors = [UIColor.red, UIColor.yellow, UIColor.green, UIColor.blue, UIColor.cyan]
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.colors.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// get a reference to our storyboard cell
if (collectionView == self.collectionViewAlert) {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollCell", for: indexPath as IndexPath)
cell.backgroundColor = self.colors[indexPath.item]
return cell
}
else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath)
cell.backgroundColor = self.colors[indexPath.item]// make cell more visible in our example project
return cell
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("You selected cell #\(indexPath.item)!")
}
}
More screens here: screens
EDIT:
I still not found answer how to solve this problem. I think problem is subview interaction, because delegate method cellForItemAt is invoked on alert show. Someone know how to figure this out? screen from view hierarchy
Thanks for any help.
I have looked into SCLAlertView code. It seems it uses a tap recognizer for dismissing the keyboard.
Tap recognizer can conflict with the tap recognizer used by collection view.
To disable the recognizer in SCLAlertView you can use an appearance object:
let appearance = SCLAlertView.SCLAppearance(
disableTapGesture: true
)
let alertView = SCLAlertView(appearance: appearance)
You can also add extension for CollectionView delegate:
extension ViewController: UICollectionViewDelegate
{
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("You selected cell #\(indexPath.item)!")
}
}
If you have added any UIImageView or UILabel inside UICollectionViewCell make sure you have enabled UserIntraction, because for both UIImageView or UILabel default as false.
setUserIntraction for both UIImageView or UILabel as TRUE.
You need to add collectionview delegate in protocol section. And be sure that you have make outlets of your object.
First of all you need to update like:
class YourViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
}
And then in viewDidLoad:
collectionView.delegate = self
collectionView.dataSource = self
collectionView.reloadData()
And make sure that your objects are properly connected.

Show/hide content in expandable collection view cell

I have a collection view with two cells. I made cells expand/collapse by select(didSelectItemAtIndexPath). Cell contains additional views which should became visible in expanded mode and hidden in collapsed. My code work well if I play with expanded cell, but if I have expanded cell and tap on collapsed the expanded cell change their size but additional view stay visible. Maybe it will be easy to understand looking on the gif.
Good scenario:
click first cell = expand it
click first cell again = collapse it and hide image view
Bag scenario:
click first cell = expand it
click second cell = expand second cell & collapse first cell but additional image view on first cell still visible
click second cell again = collapse second cell and hide their content. additional image view on first cell still visible
Here is cell code:
class ExerciseSetCell: BaseCell {
// timer
let timerButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(named: "timer"), forState: .Normal)
button.frame = CGRectMake(0, 0, 25, 25)
button.alpha = 0
return button
}()
override func setupViews() {
super.setupViews()
backgroundColor = UIColor.whiteColor()
addSubview(timerButton)
timerButton.snp_makeConstraints { (make) in
make.width.equalTo(25)
make.height.equalTo(25)
make.bottomMargin.equalTo(-7)
make.centerX.equalTo(self)
}
}
}
Here is my collectionViewController code:
class ExerciseDetailVC: UICollectionViewController, UICollectionViewDelegateFlowLayout {
var exercise: Exercise?
let exerciseSetCell = ExerciseSetCell()
private let setCell = "ExerciseSetCell"
private var expandedCellIndex = -1
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.registerClass(ExerciseSetCell.self, forCellWithReuseIdentifier: setCell)
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(setCell, forIndexPath: indexPath) as! ExerciseSetCell
return cell
}
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let cell = collectionView.cellForItemAtIndexPath(indexPath) as! ExerciseSetCell
if expandedCellIndex == indexPath.item {
UIView.animateWithDuration(0.1, animations: {
cell.weightView.alpha = 0
cell.timerButton.alpha = 0
})
expandedCellIndex = -1
} else {
UIView.animateWithDuration(0.25, animations: {
cell.weightView.alpha = 1
cell.timerButton.alpha = 1
})
expandedCellIndex = indexPath.item
}
collectionView.reloadData()
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
if expandedCellIndex == indexPath.item {
return CGSizeMake(view.frame.width, 200)
}
return CGSizeMake(view.frame.width, 60)
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat {
return 2
}

Resources