addSubview on cellForItemAtIndexPath issue in Swift - ios

I have a collectionView with custom cells.
I am trying to add dynamic labels in each cell, but they are only added to the last cell shown. If i make scroll, labels disappear of that cell and appear in the new cell shown.
The number and text of labels will be achieved with a web service.
This is my cellForItemAtIndexPath function:
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! UndatedCell
cell.titleLabel.text = "Sacar al perro"
cell.locationLabel.text = "Casa"
var aux: CGFloat = 0
for label in labelsArray {
let labelWidht = calculateLabelWidht(label) + 20
cell.labelsView.addSubview(label)
label.leftAnchor.constraintEqualToAnchor(cell.labelsView.leftAnchor, constant: aux).active = true
label.centerYAnchor.constraintEqualToAnchor(cell.labelsView.centerYAnchor).active = true
label.widthAnchor.constraintEqualToConstant(labelWidht).active = true
aux = aux + labelWidht + 8
}
return cell
}
In viewDidLoad i create the demo labels:
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView!.registerClass(UndatedCell.self, forCellWithReuseIdentifier: reuseIdentifier)
self.collectionView?.alwaysBounceVertical = true
let label1 = LabelLabel()
label1.text = "Zara"
labelsArray.append(label1)
let label2 = LabelLabel()
label2.text = "Perro"
labelsArray.append(label2)
let label3 = LabelLabel()
label3.text = "Amigo"
labelsArray.append(label3)
}
Finally, this is a pic of the result:
collectionViewPhoto
Thank you very much for your time!

Related

UITableView unexpected label changing

The cell is initially created from a xib file. Ive registered a reuse identifier in viewDidLoad().
Whenever I scroll down, one specific label is redrawn partially. Cant seem to figure out whats going on with the cell, but I know that the cell is not nil. Looking for insight on why this label isn't being drawn correctly.
before dragging
after dragging
More Info:
I can't tell, but it seems as if new cells are being drawn directly on top of the old cells. I figured this out due to the custom line divider: (let lineView = self.createLineDivider(cell: cell)) I added to context view of the cell. To stop the line from drawing over itself, I change the tag of every new cell.
override func viewDidLoad()
{
super.viewDidLoad()
let nib = UINib(nibName: "customContactDetailTableViewCell", bundle: nil)
self.contactDetailsTableView.register(nib, forCellReuseIdentifier: "customDetailCell")
}
func configureCell(cell: customContactDetailTableViewCell, indexPath: IndexPath) -> customContactDetailTableViewCell
{
let sectionName = self.props[indexPath.section].keys.first!
let contactPropLabel = self.props[indexPath.section][sectionName]![indexPath.row].keys.first!
let contactProp = self.props[indexPath.section][sectionName]![indexPath.row][contactPropLabel]
cell.contactDetailInfo?.layer.borderWidth = 1
cell.contactDetailInfo?.layer.borderColor = UIColor.white.cgColor
cell.contactDetailInfoTitle?.layer.borderWidth = 1
cell.contactDetailInfoTitle?.layer.borderColor = UIColor.white.cgColor
cell.contactDetailInfo?.numberOfLines = 0
cell.contactDetailInfoTitle?.text = contactPropLabel
cell.contactDetailInfo?.text = contactProp
cell.contactDetailInfo?.lineBreakMode = .byWordWrapping
self.rowHeight = cell.bounds.height
return cell
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{ var cell = tableView.dequeueReusableCell(withIdentifier: "customDetailCell") as? customContactDetailTableViewCell
if cell?.tag != 212
{
cell?.tag = 212
cell?.backgroundColor = UIColor.clear
let lineView = self.createLineDivider(cell: cell)
cell?.contentView.addSubview(lineView)
}
return self.configureCell(cell: cell!, indexPath: indexPath)
}
Update
Figured it out. Looks like the sporadic behavior came from the table view not updating the row height quick enough. Used this instead:
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}

While fast scrolling collectionview the first row data duplicate in the last row

i wanted to make an application with a design that looks like the app store so i followed let's build that app tutorial on how to build it and the result was excellent, when i tested it on my device it seem that when i fast scroll the collection-view the data that's in the first row take place on the last row and when scroll fast up again, the data that supposed to be in the last row i found it in the first row and when i scroll left and right in the row when the cell go off the screen it re-update to the right data but when fast scroll up and down fast again the data between the first row and last row go crazy.
i added image to the case in the end of the code.i spent 5 days trying to fix this but no luck at all i tried alot of solution like reset the data in the cell to nil before reseting it and alot other u will find it in the code but no luck , I really appreciate any help you can provide, Thanks
*update
after #Joe Daniels answer all the data are staple and work fine except the images it still go crazy when fast scrolling but it return after 7/8 sec of stopping scrolling to the right image
first class that contain the vertical collectionview
i made the width of the cell equal the width of the view , i tried the cell.tag == index-path.row solution but it didn't work
class HomePageVC: UIViewController , UICollectionViewDataSource , UICollectionViewDelegate , UICollectionViewDelegateFlowLayout ,SWRevealViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
mainProductsRow.delegate = self
mainProductsRow.dataSource = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let count = productCategory?.count {
return count + 1
}
return 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.row == 0 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: LargeHomeCategoriesCell.identifier, for: indexPath) as! LargeHomeCategoriesCell
cell.categoriesHomePageVC = self
cell.catIndexPath = indexPath.row
cell.productCategories = productCategory
cell.seeMore.addTarget(self, action: #selector(self.seeMoreCat(_:)), for: UIControlEvents.touchUpInside)
return cell
}
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "RowCell", for: indexPath) as! HomeCategoriesCell
cell.categoriesHomePageVC = self
cell.catIndexPath = indexPath.row
cell.seeMore.tag = (indexPath.row - 1 )
cell.seeMore.addTarget(self, action: #selector(self.seeMore(_:)), for: UIControlEvents.touchUpInside)
// cell.tag = indexPath.row - 1
cell.productCategory = nil
//if cell.tag == indexPath.row - 1{
cell.productCategory = productCategory?[indexPath.row - 1 ]
// }
return cell
}
**Second Class that contain the horizontal collectionview that is in the first collectionview cell **
in the cell-for-item-At-index-Path i printed the index-path.row when the view-load it printed 0 , 1 ,2 and didn't print the last index '3' and the same thing happen again when i scroll to the end of the collection view
class HomeCategoriesCell: UICollectionViewCell , UICollectionViewDataSource , UICollectionViewDelegateFlowLayout , UICollectionViewDelegate{
#IBOutlet weak var seeMore: UIButton!
#IBOutlet weak var categorytitle: UILabel!
#IBOutlet weak var productsCollectionView: UICollectionView!
let favFuncsClass = FavItemsFunctionality()
let onCartFuncsClass = OnCartFunctionality()
var productCategory : ProductCategories? {
didSet {
if let categoryTitle = productCategory?.name {
categorytitle.text = categoryTitle
}
}
override func awakeFromNib() {
recivedNotification()
productsCollectionView.delegate = self
productsCollectionView.dataSource = self
productsCollectionView.backgroundColor = UIColor.clear
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let count = productCategory?.products?.count {
return count
}
return 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
print(indexPath.row)
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "HProductCell", for: indexPath) as! HomeProductCell
cell.tag = indexPath.row
cell.productImage.image = #imageLiteral(resourceName: "PlaceHolder")
cell.productTitle.text = nil
cell.productPrice.text = nil
cell.discountLabel.text = nil
cell.preDiscountedPrice.text = nil
cell.favButton.setImage(UIImage(named:"Heart_icon"), for: UIControlState.normal)
cell.addToCart.setImage(UIImage(named:"cart"), for: UIControlState.normal)
cell.configCell(products: nil)
if cell.tag == indexPath.row {
cell.configCell(products: productCategory?.products?[indexPath.item])
}
cell.catNum = indexPath.row
return cell
}
// Thanks to Joe Daniels i added this code and my nightmare become way less scary :P
override func prepareForReuse() {
super.prepareForReuse()
productsCollectionView.reloadData()
}
}
Product Cell
i tried the prepare-For-Reuse() and reseted all the data to nil but still didn't work
class HomeProductCell: UICollectionViewCell {
func configCell(products :productDetails?){
if let title = products?.name {
self.productTitle.text = title
}else { self.productTitle.text = nil}
if let price = products?.price {
self.productPrice.text = "\(price)"
}else { self.productPrice.text = nil }
}
override func prepareForReuse() {
super.prepareForReuse()
self.productImage.image = #imageLiteral(resourceName: "PlaceHolder")
productTitle.text = nil
productPrice.text = nil
discountLabel.text = nil
preDiscountedPrice.text = nil
favButton.setImage(UIImage(named:"Heart_icon"), for: UIControlState.normal)
addToCart.setImage(UIImage(named:"cart"), for: UIControlState.normal)
}
i took a screen shoots of the case you can find it here
i Found the Perfect Solution that worked for me as magic after 15 days of suffering xD i used this library (https://github.com/rs/SDWebImage).
To use it in swift put this line in your bridging Header
#import <SDWebImage/UIImageView+WebCache.h>
And in the collectionViewCell i used this line
self.productImage.sd_setImage(with: myUrl , placeholderImage: UIImage(named:"yourPlaceHolderImage")
The inner collectionView has no way of knowing that it went off screen. do a reload in prepareForReuse

Can't add circular mask in tableView

I am creating a UITableViewCell object and returning it inside cellForRowAtIndexPath function in tableView. I have a UIView in the cell which I want to make circular. Using the code from this link, I had written following code:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell()
let containerDP = UIView()
containerDP.backgroundColor = UIColor.redColor()
// some code
let firstLetter = UILabel()
firstLetter.backgroundColor = UIColor.yellowColor()
firstLetter.text = (data[indexPath.row].UserObject.FirstName! as String).uppercaseString[0]
firstLetter.font = UIFont(name: firstLetter.font.fontName, size: 50)
firstLetter.adjustsFontSizeToFitWidth = true
firstLetter.textAlignment = NSTextAlignment.Center
firstLetter.layer.cornerRadius = firstLetter.frame.size.width / 2;
firstLetter.clipsToBounds = true
containerDP.addSubview(firstLetter)
firstLetter.snp_makeConstraints { (make) -> Void in
make.center.equalTo(containerDP)
make.edges.equalTo(containerDP).inset(UIEdgeInsetsMake(10, 10, 10, 10))
}
// some code
return cell
}
But there is still no circular mask:
Try this :
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell()
let containerDP = UIView()
containerDP.backgroundColor = UIColor.redColor()
// some code
let firstLetter = UILabel()
firstLetter.backgroundColor = UIColor.yellowColor()
firstLetter.text = (data[indexPath.row].UserObject.FirstName! as String).uppercaseString[0]
firstLetter.font = UIFont(name: firstLetter.font.fontName, size: 50)
firstLetter.adjustsFontSizeToFitWidth = true
firstLetter.textAlignment = NSTextAlignment.Center
firstLetter.layer.cornerRadius = firstLetter.frame.size.width / 2;
firstLetter.layer.masksToBounds = true
firstLetter.clipsToBounds = true
containerDP.addSubview(firstLetter)
firstLetter.snp_makeConstraints { (make) -> Void in
make.center.equalTo(containerDP)
make.edges.equalTo(containerDP).inset(UIEdgeInsetsMake(10, 10, 10, 10))
}
// some code
return cell
}
Use dequeueReusableCellWithIdentifier for tablewView cell creating and caching
Make subview setups for tableViewCell in awakeFromNib method
With clipsToBounds = true it should work
first you should create a customtableviewCell class for your tableview.
then make IBOutlet connection to your cutomtablviewCell
then #import your customtablviewCell to your tableviewController
then inside the cellForRowAtIndexPath method(I'm well in objectiveC, not in swift) call to your cutomTableviewcell like below
customTableviewCell *cell = [your code here];
after that do your styles like
cell.fisrtLetter.layer.cornerRadius = cell.firstletter.frame.size.width/2;
cell.firstLetter.layer.masksToBounds = true;
please get the idea.

PrepareForReuse in CollectionViewCell

I want to hide the label in a cell that was tapped and instead show an image. But I want to do this only if a cell with a certain index has already been set to the imageView.
What is the best way to address the cells and store if they are set to imageView or not? How do I use the prepareForReuse method?
This is the way I do it until now, but as the cells are reused. The image is shown in other cells at scrolling.
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
println("user tapped on door number \(indexPath.row)")
let cell = collectionView.cellForItemAtIndexPath(indexPath) as! MyCollectionViewCell
if (cell.myLabel.text == "1") {
one = true
if(seven = true) {
if (cell.myLabel.hidden) {
cell.myLabel.hidden = false
cell.MyImageView.image = nil
}
else {
cell.myLabel.hidden = true
cell.MyImageView.image = UIImage(named:"1")!
}
}
}
You didn't say if your collection view has exactly 7 cells or if it can have "N" (e.g. 100) cells in the collection, so if this were my problem and I had to solve it, I would make the state of your "seven" cell a property of the class (e.g. "var sevenState : Bool") and then I could display the button or image of other cells depending on what sevenState is.
In my app I have to configure a UICollectionReusableView based on the index path, if the indexPath has a particular value then I send an array which is used to set labels and images.
I use a function in the custom UICollectionReusableView, if I call it with an array it populates the labels and images and if I call it with nil it resets these.
func collectionView(collectionView: UICollectionView!, viewForSupplementaryElementOfKind kind: String!, atIndexPath indexPath: NSIndexPath!) -> UICollectionReusableView! {
.... [logic around selecting index path based on data returned]
....
if filteredEvents != nil{
reusableView.populateCalendarDayDates(sortedEvents)
}else{
reusableView.populateCalendarDayDates(nil)
}
In the function in the custom UICollectionReusableView I reset labels back to default values before possibly updating them :
func populateCalendarDayDates(arrayEvents: NSArray?){
let firstDayTag = tagStartDay()
var dayDate = 1
for var y = 1; y < 43; y++ {
let label = self.viewWithTag(y) as! BGSCalendarMonthLabel
label.delegate = callingCVC
label.backgroundColor = UIColor.clearColor()
label.textColor = UIColor.whiteColor()
label.text = ""
You can get the same effect, and it is probably a bit more readable, by moving this code to prepareForReuse in the custom UICollectionReusableView :
override func prepareForReuse() {
super.prepareForReuse()
for var y = 1; y < 43; y++ {
let label = self.viewWithTag(y) as! BGSCalendarMonthLabel
label.backgroundColor = UIColor.clearColor()
label.textColor = UIColor.whiteColor()
label.text = ""
}
}
Hope that helps.

Retaining button content in UICollectionView after scrolling with swift

I'm trying to make a UICollectionView that has infinite scrolling of buttons and the button's background is populated base on the result of http request to a server.
let reuseIdentifier = "Cell"
let screenSize: CGRect = UIScreen.mainScreen().bounds
let screenWidth = screenSize.width
let screenHeight = screenSize.height
let categoryApiUrl = "url"
let categoryImageField = "field"
class BrowseViewController: UICollectionViewController, UICollectionViewDataSource, UICollectionViewDelegate {
var categoryImgUrl:[String] = []
var buttonList:[UIButton] = []
func setupView(){
self.title = "Browse"
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 20, left: 10, bottom: 10, right: 10)
layout.itemSize = CGSize(width: screenWidth/2-15, height: screenHeight/3.5)
collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
collectionView!.dataSource = self
collectionView!.delegate = self
collectionView!.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
collectionView!.backgroundColor = UIColor.whiteColor()
self.view.addSubview(collectionView!)
}
func setupButton(cell: UICollectionViewCell, cellNumber: Int){
var button = UIButton.buttonWithType(UIButtonType.System) as UIButton
button.frame = CGRectMake(0, 0, screenWidth/2-15, screenHeight/3.5)
button.backgroundColor = UIColor.orangeColor()
button.setTitle("Category", forState: UIControlState.Normal)
button.addTarget(self, action: "btnClicked:", forControlEvents: UIControlEvents.TouchUpInside)
buttonList.append(button)
cell.addSubview(button)
}
override func viewDidLoad(){
super.viewDidLoad()
let url = NSURL(string: categoryApiUrl)
let request = NSURLRequest(URL: url!)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) {(response, dataValue, error) in
let json = JSON(data: dataValue)
for(var i = 0; i < json.count; i++){
self.categoryImgUrl.append(json[i]["CATEGORY_IMAGE"].stringValue)
let imageUrl = self.categoryImgUrl[i]
let url = NSURL(string: imageUrl)
let data = NSData(contentsOfURL: url!)
let image = UIImage(data: data!)
self.buttonList[i].setBackgroundImage(image, forState: .Normal)
}
}
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
//#warning Incomplete method implementation -- Return the number of sections
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//#warning Incomplete method implementation -- Return the number of items in the section
return 10;
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as UICollectionViewCell
let cellNumber = indexPath.row as Int
setupButton(cell, cellNumber: cellNumber)
// Configure the cell
return cell
}
override func scrollViewDidScroll(scrollView: UIScrollView) {
let offsetY = scrollView.contentOffset.y
let contentHeight = scrollView.contentSize.height
if offsetY > contentHeight - scrollView.frame.size.height {
numberOfItemsPerSection += 6
self.collectionView!.reloadData()
}
}
}
Currently, the code is able to pull the image from the server and populate it as the button's background image.
However, since I made this collection view scrollable. When I scroll the view down and then back up, the background image of the previous buttons disappear.
I did some research but couldn't find a solution to it. The reason that the button disappears is because IOS only loads the cell that is visible on screen. So when I scroll down and then scroll back up, the previous cells are consider as "New Cells". Therefore the background image that was in it are now gone.
Questions:
Does anyone have an idea on how to retain the previous buttons even if we scroll down and then scroll back up? In addition, with my current code, I added the image onto the button inside the http request because the http request is always the last execution that finishes. Is there anyway to change the code so then the http request will be finish before the cells get loaded?
I would suggest to create uicollectionview in interface builder, subclass uicollectionviewcell, add one dynamic cell to collectionview, change its class to collectionviewcell you subclassed, drop uibutton on it, and everytime cell is being created, you would download the image.
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as myCollectionViewCell
let cellNumber = indexPath.row as Int
//downloadimghere
cell.myButton.setBackgroundImage(downloadedImg, forState: .Normal)
return cell
}
This would download image everytime cell is being created. For more info you should checkout "lazy image loading". I think this is a better approach to your problem.
Now to your code, first of all you are not using your buttonList array, everytime cell is being created you create a new button and place it there, so you are not reusing already created buttons. If you fixed this, it might work like you wanted.
Here is another problem, since collectionview is reusing cells, everytime you create a button and place it on cell, it stays there, so basically now you are creating button on button. So if you want this to work correctly and have only one button on your cell, you need to remove previous button from the cell before you create it, you can do this in cellForItemAtIndexPath.
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as myCollectionViewCell
//something like this
for view in cell.subviews(){
if view == <UIButton>{
view.removeFromSuperview()
}
}
return cell
}
There might be some syntax errors in my code, I didnt test it, but you get the idea how to do it.

Resources