UIView Not Rounded, UITableView Cell Recycling - ios

I have a UIView within the cell of a UITableView. My cellForRowAtIndexPath rounds that view. When I open the viewcontroller and see the table, all the visible views are rounded. However, when I scroll down, the first new cell to appear does not have a rounded view. How do I fix it?
My cellForRowAtIndex looks like this:
let cell = tableView.dequeueReusableCell(withIdentifier: "RepeatingCell", for: indexPath) as! CustomTableViewCell
cell.delegate = self
tableView.contentInset = UIEdgeInsetsMake(10, 0, 0, 0)
if (indexPath as NSIndexPath).row < tableListInfo_Context.count {
let task = tableListInfo_Context[(indexPath as NSIndexPath).row
let accState = task.value(forKey: "state") as? String
//Removed assigning labels their text
let mainView = cell.mainView
mainView.isHidden = false
let circleView = cell.viewToRound
circleView?.isHidden = false
circleView?.layer.cornerRadius = (circleView?.frame.size.width)!/2
circleView?.layer.masksToBounds = true
cell.layoutMargins = UIEdgeInsetsMake(0, 1000, 0, 1000)
} else {
//My last cell hides several views to reveal a view under them
//If I comment this one line out, I get the not-rounded problem only once and it does not show up again even if I continue scrolling up / down
let mainView = cell.mainView
mainView.isHidden = true
let circleView = cell.viewToRound
circleView?.isHidden = true
}
I understand that UITableViews recycle cells and I think that's likely causing me problems. I think it's related to that last cell where I hide views.
In a list of 20 cells, sometimes a non-rounded view shows up as the first to scroll on screen, sometimes the second. As I scroll up/down, randomly a view isn't rounded! I know there's a reason/pattern, but I don't know what.
I also tried adding the rounding code to willDisplayCell:
let circleView = myCell.mainCircle
circleView?.layer.cornerRadius = (circleView?.frame.size.width)!/2
circleView?.layer.masksToBounds = true
However, that did not solve it. How inconsistent this is is very frustrating. Any help would be appreciated.
FULL CODE AS REQUESTED BY DUNCAN:
func buildCell_RepeatableCell (indexPath: IndexPath) -> CustomTableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "RepeatingCell", for: indexPath) as! CustomTableViewCell
cell.delegate = self
tableToSV_Left.constant = 10
tableToSV_Right.constant = 10
tableView.contentInset = UIEdgeInsetsMake(10, 0, 0, 0)
if (indexPath as NSIndexPath).row < curLifeList_Context.count {
resetDataCell_ToDefaultLaylout(cell: cell, hideExpand: true)
let task = curLifeList_Context[(indexPath as NSIndexPath).row]
let accState = task.value(forKey: "state") as? String
cell.nameLabel!.text = task.value(forKey: "name") as? String
cell.descLabel!.text = task.value(forKey: "desc") as? String
let redTrashIcon = UIImage(named: "trash")
let maskedIcon = redTrashIcon?.maskWithColor(color: .red)
cell.row5_LeftBtnImg?.image = maskedIcon
let doneGreen = UIColor(colorLiteralRed: 83/255, green: 223/255, blue: 56/225, alpha: 0.9)
let greenCheck = UIImage(named: "check_Green")
let maskedCheck = greenCheck?.maskWithColor(color: doneGreen)
cell.row5_RightBtnImg?.image = maskedCheck
roundQtyCircle(view: cell.mainCircle)
if accState == "Selected" {
cell.circleLine.backgroundColor = doneGreen
cell.mainCircle.borderColor = doneGreen
cell.hdrCrossOutLine.isHidden = false
cell.row5_RightBtnImg.alpha = 0.5
} else {
cell.circleLine.backgroundColor = UIColor(colorLiteralRed: 211/255, green: 211/255, blue: 211/255, alpha: 0.9)
cell.mainCircle.borderColor = UIColor(colorLiteralRed: 211/255, green: 211/255, blue: 211/255, alpha: 0.9)
cell.hdrCrossOutLine.isHidden = true
cell.row5_RightBtnImg.alpha = 1.0
}
cell.dataHdr_Left!.text = doneColon_lczd
cell.dataHdr_Right!.text = lastDoneColon_lczd
cell.dataVal_Left!.text = "0"
cell.dataVal_Right!.text = "-"
if let lastCompDate = task.value(forKey: "lastCompleted") as? Date {
cell.dataVal_Left!.text = "\(task.value(forKey: "timesCompleted") as! Int)"
if NSCalendar.current.isDateInToday(lastCompDate) {
cell.dataVal_Right!.text = "Today"
cell.dataBotDtl_Right.text = ""
} else {
let secondsUntilChange = (lastCompDate.seconds(from: now)) * -1
print("Seconds ago \(secondsUntilChange)")
var timeAgo = getDateString(secondsUntilChange, resetDate: lastCompDate)
timeAgo.remove(at: timeAgo.startIndex)
cell.dataVal_Right!.text = timeAgo
}
}
cell.layoutMargins = UIEdgeInsetsMake(0, 1000, 0, 1000)
} else {
buildAddNew_ForRepeatableCell(cell: cell)
}
return cell
}
func resetDataCell_ToDefaultLaylout (cell: CustomTableViewCell, hideExpand: Bool) {
cell.addnewBtn.isHidden = true
cell.nameLabel.isHidden = false
cell.dataHdr_Left.isHidden = false
cell.dataHdr_Right.isHidden = false
cell.dataTopDtl_Right.isHidden = false
cell.dataBotDtl_Right.isHidden = false
cell.mainBox.isHidden = false
}
func buildAddNew_ForRepeatableCell (cell: CustomTableViewCell) {
cell.addnewBtn.isHidden = false
cell.descLabel.text = addNew_lczd //For editor
cell.mainBox.isHidden = true
cell.layoutMargins = UIEdgeInsetsMake(0, 1000, 0, 1000)
cell.container.layoutIfNeeded()
}

Instead of setting layer properties of the view in cellForRowAtIndexPath, set them in your CustomTableViewCell class. You can set them in awakeFromNib Method.
In short move your below code to awakeFromNib.
let circleView = cell.viewToRound
circleView?.layer.cornerRadius = (circleView?.frame.size.width)!/2
circleView?.layer.masksToBounds = true
The sudo code could be something like this:
awakeFromNib(){
self.viewToRound?.layer.cornerRadius = (viewToRound?.frame.size.width)!/2
viewToRound?.layer.masksToBounds = true
}

#Adeel already told you the problem and the solution in his comment.
You always need to fully configure your cells in every case. Remember that when you get a recycled cell, it might be in any possible leftover state.
In your case, the key bit is:
if (indexPath as NSIndexPath).row < tableListInfo_Context.count {
//Configure cells that are not the last cell
} else {
//Configure cells the last cell
mainView.isHidden = true
}
You hide mainView in the case where you're configuring the last cell, but don't un-hide it if it's not the last cell. So, if you configure the last cell, scroll it off-screen, then scroll back towards the top of the screen, that recycled cell will get reused for an indexPath other than the last one, and it's mainView will never get un-hidden.
if (indexPath as NSIndexPath).row < tableListInfo_Context.count {
//Configure cells that are not the last cell
//-----------------------------
// This is what you are missing
mainView.isHidden = false
//-----------------------------
} else {
//Configure cells the last cell
mainView.isHidden = true
}
#VishalSonawane's answer is good, but to make that work you should use 2 different identifiers, one for all cells but the last one, and a different identifier, with a cell pre-configured with mainView hidden, for the last cell. That way you won't get a recycled last cell and try to use it for one of the other positions in your table view.

Try this, it will work.
override func awakeFromNib() {
super.awakeFromNib()
self.viewToRound?.layer.cornerRadius = (viewToRound?.frame.size.width)!/2
viewToRound?.layer.masksToBounds = true
}
override func prepareForReuse() {
super.prepareForReuse()
self.viewToRound?.layer.cornerRadius = (viewToRound?.frame.size.width)!/2
viewToRound?.layer.masksToBounds = true
}

Related

UITableview shuffle while scrolling

I have a UITableView with some CustomViews, I am adding everything successfully. But when I scroll the tableview it's shuffle with the the UIImageView those I have added on UIView on runtime.can you please help me out to solve this issue.
Please check my code and give me the right solution.Please prefer only swift3
if(tb_LayerType == "layer"){
var cell = tableView.dequeueReusableCell(withIdentifier: "menuitem1", for: indexPath) as! MenuItemMultipleImageTableViewCell
cell.tag = indexPath.row
print(indexPath.row)
cell.lblProductName.text = listSearch[indexPath.row].tb_product_name
cell.lblProductTopings.text = listSearch[indexPath.row].tb_topping_name
cell.selectionStyle = .none
let productSize = listSearch[indexPath.row].tb_product_size!.components(separatedBy: ",")
let productAmount = listSearch[indexPath.row].tb_product_amount!.components(separatedBy: ",")
// cell.imgView.kf.setImage(with: nil)
if(productSize[0] == "regular"){
cell.lblSize.text = "("+" Alm "+")"
cell.lblPrice.text = productAmount[productSize.index(of: "regular")!]+" kr"
}else if(productSize[0] == "small"){
cell.lblSize.text = "("+" Lille "+")"
cell.lblPrice.text = productAmount[productSize.index(of: "small")!]+" kr"
}else{
cell.lblSize.text = "( "+productSize[0].capitalized+" )"
cell.lblPrice.text = productAmount[0]+" kr"
}
extraTopingImages.removeAll()
extraTopingImages = listSearch[indexPath.row].tb_topping_image!.components(separatedBy: ",")
for name in extraTopingImages {
let image = UIImageView()
self.UrlName = ""
if let range = name.range(of: ".png") {
let firstPart = name[name.startIndex..<range.lowerBound]
self.UrlName = Constants.imageurl+firstPart+".png"
}
if let range = name.range(of: ".png") {
let tagnumber = name[range.upperBound...]
print(tagnumber)
let url = URL(string: UrlName!)
image.kf.setImage(with: url)
image.frame = CGRect(x: 0, y: 0, width: 70, height: 70)
//image.tag = Int(tagnumber)!
image.layer.zPosition = CGFloat(Int(tagnumber)!)
imageArr.append(image)
cell.viewMultipleImage.addSubview(image)
}
}
return cell
}
As you are making cell object with dequeueReusableCell, dequeueReusable means that cell will be gonna reuse again and again. So in any situation if one cell has found if let range = name.range(of: ".png"), all other cell will use same images.
What you should do in this case is, remember to put else closes with if in cellForRow method.

how to hide label?

As you can see, i have a list of collection view here, and some product are having promotion price and some are not. For those product which having promotion, it will display the red colour price with the actual price strike through with it(beside). The problem now is, i was passing all these value from previous view using segue, now i have to hide promotion price label for those product which not having promotion price, how should i do it?
hide label
Here is the code:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! SubCategoryDetailsCollectionViewCell
let grey = UIColor(red: 85.0/255.0, green: 85.0/255.0, blue: 85.0/255.0, alpha: 1.0)
cell.layer.borderWidth = 1.0
cell.layer.borderColor = grey.CGColor
cell.titleLabel.text = name[indexPath.row]
cell.imageView.sd_setImageWithURL(NSURL(string: thumbImg1[indexPath.row] ))
I try to hide the label in this way, but its not really working,
it work for awhile and after i start scrolling my collection view, all promo label is hidden
if promo[indexPath.row] == "0"{
cell.promoLabel.hidden = true
}else{
cell.promoLabel.text = "RM" + promo[indexPath.row]
}
cell.priceLabel.text = "RM" + price[indexPath.row]
cell.productLabel.text = label[indexPath.row]
cell.setNeedsDisplay()
return cell
}
try this
if promo[indexPath.row] == "0"{
cell.promoLabel.hidden = true
}else{
cell.promoLabel.hidden = false
cell.promoLabel.text = "RM" + promo[indexPath.row]
}
cell.productLabel.text = label[indexPath.row]
cell.setNeedsDisplay()
return cell
}
You can hide label by changing alpha value also. Try
cell.priceLabel.alpha = 0 //to hide
cell.priceLabel.alpha = 1.0 //to show
This problem occurs because of cell reusing
Try this code:
if promo[indexPath.row] == "0" {
cell.promoLabel.hidden = true
}
else {
cell.promoLabel.hidden = false
cell.promoLabel.text = "RM" + promo[indexPath.row]
}

Shadows in tableview keep stacking (Swift)

I currently have a list of 70 questions in a tableview. Once a question is solved, the table is updated and shows a checkmark (all works OK).
The issue I am having is with the shadow that I add to each of my cells in the tableview. For some reason they keep stacking when scrolling up and down. Also when putting the device in landscape, the new CGRect is drawn, but the old one is still there (also overlap).
The code:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:QuestionCell = tableView.dequeueReusableCellWithIdentifier("cell") as! QuestionCell
let bgColorView = UIView()
bgColorView.frame = CGRectMake(5, 5, (tableView.frame.width)-10, (cell.frame.height)-10)
bgColorView.backgroundColor = UIColor.clearColor();
cell.selectedBackgroundView = bgColorView
let myBackView=UIView(frame:cell.frame)
myBackView.frame = CGRectMake(5, 5, (tableView.frame.width)-10, (cell.frame.height)-10)
myBackView.backgroundColor = UIColor.whiteColor();
myBackView.layer.masksToBounds = false
myBackView.clipsToBounds = false
myBackView.layer.cornerRadius = 3
myBackView.layer.shadowOffset = CGSizeMake(-1, 1)
myBackView.layer.shadowRadius = 2
myBackView.layer.shadowOpacity = 0.4;
let test:CGRect = myBackView.layer.bounds
myBackView.layer.shadowPath = UIBezierPath(rect: test).CGPath
cell.addSubview(myBackView)
cell.sendSubviewToBack(myBackView)
if (question.showAfter == "true") {
cell.accessoryType = UITableViewCellAccessoryType.Checkmark
//give header of correct question blue color
cell.backgroundColor = UIColor.lightTextColor()
cell.headerQuestion.textColor = UIColor(red:0.01, green:0.53, blue:0.82, alpha:1.0)
}
else
{
cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
cell.backgroundColor = UIColor.whiteColor()
cell.headerQuestion.textColor = UIColor(red:0.38, green:0.49, blue:0.55, alpha:1.0)
}
return cell
}
I have already tried the following (it was a solution from someone on an objective-C topic):
let tag = 120
if (cell.tag != 120) {
//the above code here
cell.tag = tag
}
This solves the shadow issue, BUT when I turn the device to landscape mode it does not redraw.
Any ideas? All are welcome
Your adding a new shadow every time a cell is reused. You should only add the shadow once at creation time.
Make a subclass of UITableViewCell and do your common customizations (background color, shadow) there. Then register your class with the tableview. Only do stuff that's different per cell in tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
Try this :
if(cell.viewWithTag(120)==nil)
{
let myBackView=UIView(frame:cell.frame)
myBackView.frame = CGRectMake(5, 5, (tableView.frame.width)-10, (cell.frame.height)-10)
myBackView.backgroundColor = UIColor.whiteColor();
myBackView.layer.masksToBounds = false
myBackView.clipsToBounds = false
myBackView.layer.cornerRadius = 3
myBackView.layer.shadowOffset = CGSizeMake(-1, 1)
myBackView.layer.shadowRadius = 2
myBackView.layer.shadowOpacity = 0.3;
let test:CGRect = myBackView.layer.bounds
myBackView.layer.shadowPath = UIBezierPath(rect: test).CGPath
myBackView.tag = 120;
cell.addSubview(myBackView)
}
cell.sendSubviewToBack(myBackView)

Swift redraw view

I have a theme class with a few fields.
Background color, font color and element color.
This class also has few static variations of those
class Themes {
static let defaultTheme = Themes(topColor: "5856D6",bottomColor: "C644FC",elementColor: "1F1F21",fontColor: "2B2B2B", name:"one")
static let defaultTheme1 = Themes(topColor: "5AD427",bottomColor: "FFDB4C",elementColor: "1F1F21",fontColor: "2B2B2B", name:"two")
static let defaultTheme2 = Themes(topColor: "FB2B69",bottomColor: "FF5B37",elementColor: "1F1F21",fontColor: "2B2B2B", name:"three")
static let defaultTheme3 = Themes(topColor: "52EDC7",bottomColor: "5AC8FB",elementColor: "1F1F21",fontColor: "2B2B2B", name:"four")
static let defaultTheme4 = Themes(topColor: "5AD427",bottomColor: "FFDB4C",elementColor: "1F1F21",fontColor: "2B2B2B", name:"five")
let topColor:UIColor
let bottomColor:UIColor
let elementColor:UIColor
let fontColor:UIColor
}
My view controller loads the first one by default.
override func viewDidLoad() {
super.viewDidLoad()
self.initView(ThemeManagement.sharedInstance.getTheme())
}
I have a button which changes the theme by going to the next one of the defaults and looping around back to defaultTheme after defaultTheme4
The original run through inside initView is setting the view correctly. But each pass does not update the view. Despite the log messages of the function being printed.
func initView(objectColor : Themes){
println("initing theme "+objectColor.name+"\n"+objectColor.topColor.debugDescription)
let background = CAGradientLayer().gradient(objectColor.topColor, bottomColorCode: objectColor.bottomColor)
background.frame = self.view.bounds
self.view.layer.insertSublayer(background, atIndex: 0)
codes.delegate = self
codes.placeholder = "####"
codes.textAlignment = NSTextAlignment.Center
codes.font = Themes.defaultThemeWithFontSize(.H2)
codes.textColor = objectColor.fontColor
segments.tintColor = objectColor.elementColor
segments.setTitle(String.fontAwesomeIconWithName(.Male), forSegmentAtIndex: 0)
segments.setTitle(String.fontAwesomeIconWithName(.VenusMars), forSegmentAtIndex: 1)
segments.setTitle(String.fontAwesomeIconWithName(.Female), forSegmentAtIndex: 2)
var font = UIFont.fontAwesomeOfSize(fontsize/2);
var attr = NSDictionary(object: font, forKey: NSFontAttributeName)
segments.setTitleTextAttributes(attr as [NSObject : AnyObject], forState:UIControlState.Normal)
acceptButton.titleLabel?.font = UIFont.fontAwesomeOfSize(fontsize)
acceptButton.setTitle(String.fontAwesomeIconWithName(FontAwesome.ArrowCircleORight), forState: .Normal)
acceptButton.tintColor = UIColor.greenColor()
workedButton.setTitle("yes", forState: UIControlState.Normal)
workedButton.tintColor = objectColor.fontColor
workedButton.layer.cornerRadius = 5
workedButton.titleLabel?.font = Themes.defaultThemeWithFontSize(.H2)
workedButton.backgroundColor = UIColor(red:0.333, green:0.937, blue:0.796, alpha: 0.2)
didntWorkButton.setTitle("no", forState: UIControlState.Normal)
didntWorkButton.tintColor = objectColor.fontColor
didntWorkButton.layer.cornerRadius = 5
didntWorkButton.titleLabel?.font = Themes.defaultThemeWithFontSize(.H2)
didntWorkButton.backgroundColor = UIColor(red:0.333, green:0.937, blue:0.796, alpha: 0.2)
locationButton.setTitle("Current Location", forState: UIControlState.Normal)
locationButton.tintColor = objectColor.fontColor
locationButton.layer.cornerRadius = 5
locationButton.titleLabel?.font = Themes.defaultThemeWithFontSize(.H3)
didItWork.text = "Did it work?"
didItWork.font = Themes.defaultThemeWithFontSize(.H2)
didItWork.textColor = objectColor.fontColor
self.view.setNeedsDisplay()
}
Do I have to do something else to get the background layer to update and the font colors to change?
by this
self.view.layer.insertSublayer(background, atIndex: 0)
every time you insert a new background layer behind the old one so you won't see it.
and change the tintColor of a view won't change the view's color immediately so try to change it's textColor direacly
Edit by cripto
The answer above is correct. I just wanted to add my solution to it.
I simply keep a reference to the layer and remove it as long as its not null. This guarantees that I can set it on the first pass and change it every time there after.
if((backgroundLayer) != nil){
backgroundLayer.removeFromSuperlayer()
}
backgroundLayer = CAGradientLayer().gradient(objectColor.topColor, bottomColorCode: objectColor.bottomColor)
backgroundLayer.frame = self.view.bounds
self.view.layer.insertSublayer(backgroundLayer, atIndex: 0)

Subview in cell loses color when tableview is changing

I have an UITableView which has a dynamic subview.
When the table is static it looks like this:
The round view with the T is the custom subview
But when I choose edit and drag the table cell the it looses it's color and the T.
Whats the reason for this?
I initialize the cell like this (It's an prototype IB Cell):
func configureCell(cell: UITableViewCell, atIndexPath indexPath: NSIndexPath) {
let object = self.fetchedResultsController.objectAtIndexPath(indexPath) as Item
//cell.textLabel.text = object.valueForKey("name")!.description
let cSubView = cell.viewWithTag(100) as RoundedIcon
cSubView.setUpViewWithFirstLetter(String(first(object.name)!).uppercaseString)
}
And the RoundedIcon works like this:
override init(frame: CGRect) {
super.init(frame: frame)
self.layer.cornerRadius = self.frame.size.width / 2;
self.layer.borderWidth = 1.0;
self.layer.borderColor = UIColor.lightGrayColor().CGColor;
self.clipsToBounds = true;
}
func setUpViewWithFirstLetter(letter:String){
self.backgroundColor = RoundedIcon.UIColorFromRGB(0x68C3A3)
let theLetterLabel = UILabel()
theLetterLabel.text = letter
theLetterLabel.textColor = UIColor.whiteColor()
theLetterLabel.textAlignment = .Center
theLetterLabel.font = UIFont.systemFontOfSize(25)
self.addSubview(theLetterLabel)
theLetterLabel.frame = CGRect(origin: CGPoint(x: 0, y: 0), size: self.frame.size)
}
#rdelmar's comment pointed me in the right direction that an UITableview changes the background color of all it's cells to:
UIColor(white:0,alpha:0)
If you don't want your view to change it's color you should change the backgroundColor property setter, which works in swift like this:
//override this setter to avoid color resetting on drag
override var backgroundColor:UIColor?{
didSet {
//check the color after setting - you also can do it earlier
if let bgCol = backgroundColor{
println(bgCol)
if bgCol == UIColor(white: 0, alpha: 0){ //check if it's settled by the table
super.backgroundColor = yourColor //set it back to your color
}
}
}
}

Resources