UITableView that switch cells - ios

I want to have a tableview that has 2 cells.
One with a label that increases the height and one without that label. The user adds things to this tableview (sort of like a todo list) and if the user selects an option, the label is visible in the tableviewcell of the new item that they added, but if they don't select the option, then they add a tableviewcell without that label. Is there anyway in which I can choose when to display the cell with the label.
I first thought of just making the label invisible if the option isn't selected, however that affect the height of the other label and the constraints.
Would it be possible to create 2 different cells with different identifiers, and call which one to add depending on whether the option is selected.
According to solutions, I tried switching between 2 different cells and this was my code for doing so.
if newMode==true{
let cell = tableView.dequeueReusableCell(withIdentifier: "monthlyCell", for: indexPath) as! MonthlyExpenseTableViewCell
let screenSize: CGRect = UIScreen.main.bounds
cell.dateLabelBackground.frame = CGRect(x: 0, y: 0, width: screenSize.width, height: 31)
cell.dateLabel.text = dateString
cell.expenseName2.text = expense.name
cell.expenseAmount2.text = finalDisplayed
cell.expenseCategory2.text = expense.category
cell.expenseCollection2.text = expense.collection
if (expense.expense) {
cell.expenseAmount2.textColor = UIColor.red
}
else if (expense.income){
cell.expenseAmount2.textColor = UIColor.green
}
if (expense.cash) && (expense.expense){
cell.cashOrCredit.image = #imageLiteral(resourceName: "Cash-Expense Icon")
}
else if (expense.cash) && (expense.income){
cell.cashOrCredit.image = #imageLiteral(resourceName: "Cash-Income Icon")
}
else if (expense.credit) && (expense.income){
cell.cashOrCredit.image = #imageLiteral(resourceName: "Credit-Income Icon")
}
else if (expense.credit) && (expense.income){
cell.cashOrCredit.image = #imageLiteral(resourceName: "Credit-Expense Icon")
}
}
else if newMode==false{
let cell = tableView.dequeueReusableCell(withIdentifier: "monthlyCell2", for: indexPath) as! MonthlyExpenseTableViewCellShort
cell.expenseName3.text = expense.name
cell.expenseAmount3.text = finalDisplayed
cell.expenseCategory3.text = expense.category
cell.expenseCollection3.text = expense.collection
if (expense.expense) {
cell.expenseAmount3.textColor = UIColor.red
}
else if (expense.income){
cell.expenseAmount3.textColor = UIColor(red:0.49, green:0.83, blue:0.13, alpha:1.0)
}
if (expense.cash) && (expense.expense){
cell.cashOrCredit3.image = #imageLiteral(resourceName: "Cash-Expense Icon")
}
else if (expense.cash) && (expense.income){
cell.cashOrCredit3.image = #imageLiteral(resourceName: "Cash-Income Icon")
}
else if (expense.credit) && (expense.income){
cell.cashOrCredit3.image = #imageLiteral(resourceName: "Credit-Income Icon")
}
else if (expense.credit) && (expense.income){
cell.cashOrCredit3.image = #imageLiteral(resourceName: "Credit-Expense Icon")
}
}
return cell
}
The problem with this, is that it only returns on cell at a time for some reason. When I delete the cell, it shows the next one.
Here is the code for me setting the boolean "newMode"
if indexPath.row == 0{
newMode = true
}
if indexPath.row>1{
let previousExpensesData = monthlyExpenses[indexPath.row - 1].modificationDate
let day = Calendar.current.component(.day, from: expense.modificationDate as! Date) // Do not add above 'date' value here, you might get some garbage value. I know the code is redundant. You can adjust that.
let previousDay = Calendar.current.component(.day, from: monthlyExpenses[indexPath.row - 1].modificationDate! as Date)
if day == previousDay {
newMode = false
} else {
newMode = true
}
}
For my heightForRowAtIndexPath, I use this code
func tableView(_ tableView: UITableView,
heightForRowAt indexPath: IndexPath) -> CGFloat
{
if newMode==true {
return 95
}
else if newMode==false {
return 64
}
else {
return 0
}
}
And this is the code for numberOfRows
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if monthlyExpenses.count == 0{
noExpenseLabel.isHidden = false
}
return monthlyExpenses.count
}
Here are what the 2 cells look like

Actually you will no need of having two different cell. As you just said in your question "I first thought of just making the label invisible if the option isn't selected, however that affect the height of the other label and the constraints."
Just use one cell.
Now to overcome this thing use UIStackView.Put that label in this stackView. Give proper constraints to stackView. And hide/unhide the label according to your option. UIStackView will do the things for you. :)
It will give you the result as you want. For more information about UIStackView, read this awesome tutorial by Raywenderlich.
EDIT: As per your comment
I have created a cell like this..
I managed all subviews of that cell like this..
And I just wrote a line of code in cellForRow
cell.label.isHidden = indexPath.row % 2 == 1
And I'm getting the output this....

Related

iOS: Xib View is moving outside the screen from right side

I am using Xib view as a common view in header as shown in image
When user move to next view controller and came back to previous screen it moves out side of screen as shown in image
I have auto layout the view and given 10 points margin from right side to arrow image. I am not sure why it is moving to right side.
Following is the code:
func setHeader(headerView : UIView, vc : UIViewController)
{
vc1 = vc
customView.removeFromSuperview()
customView = UINib(nibName:"ViewHeader",bundle:.main).instantiate(withOwner: nil, options: nil).first as! HeaderView
customView.btnNotification.setTitle("", for: .normal)
customView.btnReward.setTitle("", for: .normal)
customView.btnSideMenu.setTitle("", for: .normal)
customView.viewUnread.layer.cornerRadius = customView.viewUnread.frame.size.width/2
customView.viewUnread.clipsToBounds = true
customView.btnSideMenu.addTarget(self, action: #selector(Commons.openDrawer(_:)), for: .touchUpInside)
customView.lblPoints.text = String(Constants.CUSTOMERPOINTS)
customView.btnReward.addTarget(self, action: #selector(Commons.openReward(_:)), for: .touchUpInside)
customView.btnNotification.addTarget(self, action: #selector(Commons.openNotification(_:)), for: .touchUpInside)
var w = String(format: "%f", Commons.screenWidth)
var h = String(format: "%f", Commons.screenHeight)
print("screenSize ye ha --> height: " + w + " - Width:" + h)
if(Commons.screenHeight > 400 && Commons.screenHeight <= 750 )
{
customView.frame.size.height = 80.0
}
else if(Commons.screenHeight > 750 && Commons.screenHeight <= 850 )
{
// customView.ic_centerMargin.constant = -20
customView.frame.size.height = 80.0
}
else if(Commons.screenHeight > 850 && Commons.screenHeight <= 900 )
{
customView.frame.size.height = 80.0
}
else if (Commons.screenHeight > 900)
{
customView.frame.size.height = 80.0
}
let group = DispatchGroup()
group.enter()
Commons.getPoints()
{ [self] json, error in
// will be called at either completion or at an error.
// will be called at either completion or at an error.
DispatchQueue.main.async { [self] in
customView.lblPoints.text = String(Constants.CUSTOMERPOINTS)
}
group.leave()
}
group.wait()
headerView.addSubview(customView)
// customView.center = vc1.view.center;
}
Thanks in advance

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.

UITableViewCell Delete content

I am having trouble with the reusable aspect of my UITableViewCell. I have different type of data going into the different cells. When the types of data are the same (example: only images) I have no problem because the new image over writes the old one. But when the types of data are the same they both are sticking around.
I'm looking for methods or tools for clearing a cell completely. I have tried many things such as dictionaries to reference the data directly and erase it but that failed because I couldn't consistently tell what data was in what reused cell (especially when scrolling a lot). Others things were
cell.contentView.subviews.removeAll()
threw error, another was
cell.contentView.removeFromSuperView()
but that just made the cell useable and nothing loaded ever.
my code is below, thanks if advance for the help (ignore the date stuff).
if dictionary?["type"]! != nil {
cell.imageView?.layer.borderColor = UIColor.black.cgColor
cell.layer.borderWidth = 1
cell.imageView?.layer.cornerRadius = 1
cell.imageView?.frame = CGRect(x: 0, y: UIScreen.main.bounds.height/2, width: 25, height: 25)
self.tableTextOverLays(i: indexPath.row, type: Int((dictionary?["type"])! as! NSNumber), cell: cell)
if (indexPath.row == tableFeedCount - 1) && lockoutFeed != true && tableFeedCount == self.allDictionary.count && imageFeedCount == imageDictionary.count {
let dictionary = self.allDictionary[indexPath.row]
let date = dictionary?["date"] as! Double
let date2 = date*100000
let date3 = pow(10,16) - date2
let date4 = String(format: "%.0f", date3)
self.setUpFeed(starting: date4, queryLimit: UInt(11))
}
}
else if (dictionary?["type_"] as! String) == "Image" {
print("image feed", indexPath.row)
cell.imageView?.layer.borderColor = UIColor.black.cgColor
cell.layer.borderWidth = 1
cell.imageView?.layer.cornerRadius = 1
cell.imageView?.frame = CGRect(x: 0, y: UIScreen.main.bounds.height/2, width: 25, height: 25)
cell.imageView?.image = imageDictionary[indexPath.row]
if indexPath.row == tableFeedCount - 1 && lockoutFeed != true && tableFeedCount == self.allDictionary.count && imageFeedCount == imageDictionary.count{
let dictionary = self.allDictionary[indexPath.row]
let date = dictionary?["date"] as! Double
let date2 = date*100000
let date3 = pow(10,16) - date2
let date4 = String(format: "%.0f", date3)
setUpFeed(starting: date4, queryLimit: UInt(11))
}
}
else if (dictionary?["type_"] as! String) == "Video" {
print("video feed", indexPath.row)
cell.imageView?.layer.borderColor = UIColor.black.cgColor
cell.layer.borderWidth = 1
let imagerView = UIImageView()
imagerView.layer.cornerRadius = 1
imagerView.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
imagerView.image = videoDictionary[indexPath.row]
// let vidUIView = UIView()
// vidUIView.tag = indexPath.row
let asf = UITapGestureRecognizer(target: self, action: #selector(ProfileController.playVid2(gestureRecognizer:)))
asf.numberOfTapsRequired = 2
// imagerView.addGestureRecognizer(asf)
let adsf = UITapGestureRecognizer(target: self, action: #selector(ProfileController.playVid(gestureRecognizer:)))
adsf.numberOfTapsRequired = 1
adsf.require(toFail: asf)
// imagerView.addGestureRecognizer(adsf)
cell.contentView.addGestureRecognizer(asf)
cell.contentView.addGestureRecognizer(adsf)
cell.contentView.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
cell.contentView.addSubview(imagerView)
//cell.contentView.addSubview(vidUIView)
if indexPath.row == tableFeedCount - 1 && lockoutFeed != true && tableFeedCount == self.allDictionary.count && videoFeedCount == videoDictionary.count{
let dictionary = self.allDictionary[indexPath.row]
let date = dictionary?["date"] as! Double
let date2 = date*100000
let date3 = pow(10,16) - date2
let date4 = String(format: "%.0f", date3)
setUpFeed(starting: date4, queryLimit: UInt(11))
}
}
It's unclear if that code is in a view controller or in a UITableViewCell subclass. In any case, you will want to use a UITableViewCell subclass for your cells and do cleanup tasks in prepareForReuse:
override func prepareForReuse() {
super.prepareForReuse()
imagerView.image = nil
}
This will involve removing any data from the cell that you don't want carried over into the cell when it is reused. Clearing images from image views, hiding views that won't be shown for all cell types, etc.
Another solution would be to have multiple cell classes and load the appropriate cell class based on the data type it is responsible for displaying. This will simplify some of the code above and may help to prevent reuse, but it will require a significant refactor by the looks of things.

UIView Not Rounded, UITableView Cell Recycling

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
}

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]
}

Resources