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)
Related
Below is my custom cell class:
class AthleteTableViewCell: UITableViewCell {
var myLabel1: UILabel!
var myLabel2: UILabel!
var profile: UIImageView!
var star = StarButton()
var touched = false
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:)")
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
let gap : CGFloat = 10
let labelHeight: CGFloat = 30
let labelWidth: CGFloat = 150
let lineGap : CGFloat = 5
let label2Y : CGFloat = gap + labelHeight + lineGap
myLabel1 = UILabel()
myLabel1.frame = CGRect(x: gap, y: gap, width: labelWidth, height: labelHeight)
myLabel1.textColor = UIColor.black
contentView.addSubview(myLabel1)
myLabel2 = UILabel()
myLabel2.frame = CGRect(x: gap * 5, y: label2Y, width: labelHeight, height: labelHeight)
myLabel2.textColor = UIColor.white
myLabel2.textAlignment = .center
myLabel2.backgroundColor = UIColor.flatMint()
myLabel2.layer.cornerRadius = 15.0
myLabel2.clipsToBounds = true
contentView.addSubview(myLabel2)
profile = UIImageView()
profile.image = UIImage()
profile.frame = CGRect(x: bounds.width - gap, y: bounds.height / 2, width: bounds.height * 1.25, height: bounds.height * 1.25)
profile.layer.cornerRadius = (bounds.height * 1.25) / 2
profile.layer.masksToBounds = true
contentView.addSubview(profile)
if (touched != false) {
star.isSelected = true
star.frame = CGRect(x: gap, y: label2Y, width: labelHeight, height: labelHeight)
contentView.addSubview(star)
} else {
star.frame = CGRect(x: gap, y: label2Y, width: labelHeight, height: labelHeight)
contentView.addSubview(star)
star.isEnabled = true
}
}
}
and below is the method for creating my cells:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = AthleteTableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "myCell")
cell.myLabel1.text = "\(myArray[indexPath.row])"
cell.myLabel1.textColor = UIColor.white
cell.myLabel2.isHidden = true
cell.profile.image = UIImage(named: cell.myLabel1.text!)
cellArray.append(cell)
if (cell.touched) {
cell.star.isSelected = true
} else {
cell.star.isOpaque = true
}
cell.backgroundColor = UIColor(white: 1, alpha: 0.2)
return cell
}
and below is the method for selecting a cell which trips an animation that I would like the final state of to persist on the tableview cell.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
makeSelection(tableView, didSelectRowAt: indexPath)
}
func makeSelection(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! AthleteTableViewCell
cell.selectionStyle = UITableViewCellSelectionStyle.none
if(selectionArray.contains(myArray.object(at: indexPath.row))) {
//Popping selection off of the stack and updating all labels
updateLabels(tableView: tableView)
selectionArray.remove(myArray[indexPath.row])
cell.star.isFavorite = false
cell.myLabel2?.isHidden = true
//need to somehow implement all of the rank labels decreasing by one if I deselect someone
cell.touched = false
//updateLabels(tableView: tableView)
} else {
selectionArray.add(myArray[indexPath.row])
cell.touched = true
let rank = selectionArray.count
cell.myLabel2.isHidden = false
cell.myLabel2?.text = "\(rank)"
cell.star.isFavorite = true
}
}
This is a photo of what a cell looks like when you select it:
and this is a photo of that same cell after scrolling down so that is out of view and then scrolling back:
Something clearly goes very wrong here - I added that "touched" boolean to the custom table view cell class under the assumption that maybe the cells were being redrawn and there was no way for it to know whether it should have that star animated or not so it was considered nil but that fix doesn't seem to work (maybe I am onto something but implemented it incorrectly?)
Let me know if you have any clue what is going on here! thanks so much!!!
Your code has a variety of problems. In your tableView(_:cellForRowAt:) method, you should be calling dequeueReusableCell(withIdentifier:for:).
You should not be trying to read data from the cells in order to load content. You should save state data to a data model of some sort. An array works well for a table view with a single section, and an array of arrays works well for a sectioned table view.
There are countless tutorials on how to use table views. You need to go do some reading.
Mentioning it as an answer here as a step by step process
1) Create Struct for your data
struct Profile {
var personName: String?
var photoURL: String?
var rank: String?
var isTouched: Bool?
// add init method and stuff
}
2) Populate your array using these Profile elements. So that in the end you have an array of profiles.
3) Use this array to populate your tableview with each cell element getting data from the Profile object.
4) in your cellForRowAt method use dequeueReusableCell(withIdentifier:for:) to initialize your cell and assign values
5) if for that particular profile, isTouched is true then show
cell.star.isFavorite = true
else set it to false
6) in your didSelectRow, get indexpath.row and then in your array of profiles set Profile.isTouched=true/false based on your needs for that particular row and toggle accordingly. Update your profile array with new profile object.
7) refresh tableview.
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
}
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]
}
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
}
}
}
}
In Xcode 6 Beta 5, I had a chat interface that looks like the iOS 7 messages app, where the UILabel that the text was inside sized to the width of the text itself. When I updated to Beta 6, I noticed an option for UILabel in interface builder that I hadn't noticed before:
When I have the explicit width set, the width doesn't change at all based on the width of the text. When I uncheck explicit, the width of the text is at least 234, so it expands out of the view.
I am using a UICollectionView inside of a UIViewController, and here is my cell for item at index path method:
func collectionView(collectionView: UICollectionView!, cellForItemAtIndexPath indexPath: NSIndexPath!) -> UICollectionViewCell! {
let defaults = NSUserDefaults.standardUserDefaults()
let row = indexPath.row
var cell: UICollectionViewCell
let path = UIBezierPath()
let object: AnyObject = (messages[row] as NSDictionary).objectForKey("user_id")!
let uid: AnyObject = defaults.objectForKey("user_id")!
if "\(object)" == "\(uid)" {
cell = collectionView.dequeueReusableCellWithReuseIdentifier(right_chat_bubble, forIndexPath: indexPath) as UICollectionViewCell
path.moveToPoint(CGPointMake(0, 0))
path.addLineToPoint(CGPointMake(0, 10))
path.addLineToPoint(CGPointMake(12, 5))
path.addLineToPoint(CGPointMake(0, 0))
}
else {
cell = collectionView.dequeueReusableCellWithReuseIdentifier(left_chat_bubble, forIndexPath: indexPath) as UICollectionViewCell
path.moveToPoint(CGPointMake(0, 5))
path.addLineToPoint(CGPointMake(12, 10))
path.addLineToPoint(CGPointMake(12, 0))
path.addLineToPoint(CGPointMake(0, 5))
}
let initial_view = cell.viewWithTag(101) as UILabel
initial_view.layer.cornerRadius = 20
initial_view.layer.masksToBounds = true
let name = (messages[row] as NSDictionary).objectForKey("name")! as String
let name_array = name.componentsSeparatedByString(" ")
let first_initial = name_array[0]
let last_initial = name_array[1]
let first_char = first_initial[0]
let last_char = last_initial[0]
let initials = first_char + last_char
initial_view.text = initials
let circle: UIView = cell.viewWithTag(103)! as UIView
let mask = CAShapeLayer()
mask.frame = circle.bounds
mask.path = path.CGPath
circle.layer.mask = mask
let message = cell.viewWithTag(102) as ChatLabel
message.enabledTextCheckingTypes = NSTextCheckingType.Link.toRaw()
message.delegate = self
message.text = (messages[row] as NSDictionary).objectForKey("content")! as String
message.layer.cornerRadius = 15
message.layer.masksToBounds = true
message.userInteractionEnabled = true
return cell
}
take a look at sizeThatFits: and sizeToFit:
https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIView_class/index.html#//apple_ref/occ/instm/UIView/sizeThatFits:
https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIView_class/index.html#//apple_ref/occ/instm/UIView/sizeToFit
A UILabel can call sizeThatFits like :
myLabelLbl.text = #"some text"
CGSize maximumLabelSize = CGSizeMake(200, 800)
CGSize expectedSize = [myLabelLbl sizeThatFits:maximumLabelSize]
myLabelLbl.frame = CGRectMake(0, 0, expectedSize.width, expectedSize.height) //set the new size