I'm calculating trailing constraints for stack view which is inside table view. It seems that calculation doesn't give exact value. Please refer to screenshot below. Is my calculation/logic incorrect? How to fix this so that I can get exact value?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "tableCellID", for: indexPath) as? TableViewCell else {
fatalError("Can't find cell")
}
let profile = array[indexPath.row]
if profile.isDisabled1 || profile.isDisabled2 || profile.isDisabled3 {
let totalHiddenViews: Int = (profile.isDisabled1 ? 1 : 0) + (profile.isDisabled2 ? 1 : 0) + (profile.isDisabled3 ? 1 : 0)
let singleViewWidth = (UIScreen.main.bounds.size.width - 32)/3 // 32 is sum of leading and trailing constraints
cell.stackViewTrailing.constant = CGFloat(totalHiddenViews) * (singleViewWidth + 8)
} else {
cell.stackViewTrailing.constant = 8
}
cell.view1.isHidden = profile.isDisabled1
cell.view2.isHidden = profile.isDisabled2
cell.view3.isHidden = profile.isDisabled3
return cell
}
You also have issues with the 2-cell layout. My instincts say that one of your magic numbers is incorrect and the one I see here that is directly contingent on the number of visible cells is this line.
cell.stackViewTrailing.constant = CGFloat(totalHiddenViews) * (singleViewWidth + 8)
I would start by checking the sanity of that constant and work back from there starting with code that depends on the number of visible cells.
Related
I am creating an app that contains lists which will be valid for a certain amount of time. For that I am creating a countdown bar animation on each cell. Each cell has it's own custom duration after which the cell displays "Time's Up". The cell even shows the time in seconds as it ticks down to 0 and then displays "Time's Up" message.
Right above this, there is a countdown bar animation. I am controlling all these animations from the viewController and not doing these in the customView cell as cell reusability makes the timers go haywire.
I am using two timers:
Timer() : that invokes every 1 second. This is used for the simple seconds countdown. I am using an array of type struct that houses two variables durationTime & width. The width is set to 400 by default. The durationTime is set by the user.
#objc func handleCountdown() {
let arrayLength = duration.count
var i: Int = 0
var timeCount: Int = 0
for _ in 0..<arrayLength {
let value = duration[i].durationTime - 1 //Decreasing the timer every second
let indexPath = IndexPath(row: i, section: 0)
if (value > 0)
{
duration[i].durationTime = value
if let cell = listTableView.cellForRow(at: indexPath) as! listTableViewCell? {
cell.testlabel.text = "\(value)"
}
//listTableView.reloadRows(at: [indexPath], with: .automatic)
i = i + 1
} else {
if let cell = listTableView.cellForRow(at: indexPath) as! listTableViewCell? {
cell.testlabel.text = "Time's Up"
}
i = i + 1
timeCount = timeCount + 1
}
}
if (timeCount == (arrayLength - 1)) {
self.timer?.invalidate()
}
}
The above code is invoked every second. It decrements the value of the time, and then displays it in the tableview cell. The above code works fine as it should.
CADisplayLink: this timer is used to run the progress bar animation. In this case however, I am calculating the elapsed time and dividing it by the durationTime of each element and calculating a percentage. This percentage is used to update the value of width of each progress bar. (Keep in mind that durationBar is simply duplicate of duration array. In this array however, the durationTime is not being decremented -> Just for testing purposes) Here is the code:
#objc func handleProgressAnimation() {
let currenttime = Date()
let elapsed = currenttime.timeIntervalSince(animationStartDate)
let totalWidth: Double = 400.0
print(elapsed)
let arrayLength = duration.count
var i: Int = 0
for _ in 0..<arrayLength {
let indexPath = IndexPath(row: i, section: 0)
let percentage = (elapsed / Double(durationBar[i].durationTime))
let newWidth = Double(totalWidth - (totalWidth * percentage))
durationBar[i].width = newWidth
if let cell = listTableView.cellForRow(at: indexPath) as! listTableViewCell? {
cell.timeRemainingView.frame.size.width = CGFloat(durationBar[indexPath.row].width)
}
i = i + 1
}
}
Question: After some time, some progress bars just disappear from the cells even though the time has not been completed. It occurs most often once I have done some scrolling on the tableview.
This is willDisplayCell & cellForRowIndexPath:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellid, for: indexPath) as! listTableViewCell
return cell
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let cell:listTableViewCell = cell as! listTableViewCell
cell.testlabel.text = "\(duration[indexPath.row].durationTime)"
cell.timeRemainingView.frame.size.width = CGFloat(duration[indexPath.row].width)
}
As you can in pictures below, after sometime, some progress bars vanished even though there is still some time left:
What is the issue here, I am controlling all the animations and timers from the viewController and not the cell in order to prevent cell reuseability to become a problem. Help is needed!
The thing is that you update cell.timeRemainingView.frame.size.width with
durationBar[indexPath.row].width for handleProgressAnimation and
duration[indexPath.row].width for willDisplayCell.
And also I would switch to tableView.indexPathsForVisibleRows for updating UI only for visible cells so it wouldn't call .cellForRow for every cell in if-let.
I'm using the following code to determine if the last screen is visible on the screen to the user:
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath.row == collection.count - 1 {
print("last cell is seen!!")
print(cell.frame)
}
}
This works on small screens where the user has to scroll to get to the desired cell. However, on large screens where the whole table is visible, this method doesn't work. Is there any way to determine the distance between bottom of screen to the last cell in the UITableView?
Any help would be greatly appreciated. Thank you.
Edit: this is what I'm trying to accomplish.
if (last cell is visible && it is more than 100dp from bottom of screen) { display a fixed button on bottom of screen } else { add button to footer view of tableview so the user can scroll down to the button }
try it ! Hope to help you.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let contentOffsetMaxY: Float = Float(scrollView.contentOffset.y + scrollView.bounds.size.height)
let contentHeight: Float = Float(scrollView.contentSize.height)
let ret = contentOffsetMaxY > contentHeight - 100
if ret {
print("testButton is show");
}else{
print("testButton is hidden");
}
}
Swift 5
if let visiblePaths = tableView.indexPathsForVisibleRows,
visiblePaths.contains([0, dataSourceArray.count - 1]) {
// last cell is at least partially visible
}
indexPathsForVisibleRows is an array of IndexPath. However, there are a number of ways to express IndexPath (it has 7 initializers). indexPathsForVisibleRows utilizes IndexPath as an array literal (i.e. [0, 0] or [section 0, row 0]); therefore, to use indexPathsForVisibleRows, you must declare the path as an array literal and not in any other way (i.e. IndexPath(row: 0, section: 0)).
I just created a project for you to show you one possible solution (there are many).
Basically you just need to get the last cell position, using
guard let lastCell = tableView.cellForRow(at: IndexPath(row: tableView.numberOfRows(inSection: 0)-1, section: 0)) else {
return
}
let lastCellBottomY = lastCell.frame.maxY
Then knowing the tableView height, you can easily calculate the distance between the last cell and the bottom of the screen:
let delta = tableHeight - lastCellBottomY
And check if this distance is enough for you to show or not the fixed button:
if delta > 55 {
// ...show button
} else {
// ...hide button
}
For the dynamic button (the one you want to add below the last cell of the tableView when user scrolls), you could just add a new section with a custom cell to represent your button.
You can check out the whole idea via this link :
https://www.dropbox.com/s/7wpwv6737efnt5v/ButtonTable.zip?dl=0
Let me know if you have any questions :)
You can use this method to see if the last cell is visible or not
func isCellVisible(section:Int, row: Int) -> Bool {
guard let indexes = self.indexPathsForVisibleRows else {
return false
}
return indexes.contains {$0.section == section && $0.row == row }
} }
Source
Then you can call it like
let lastCell = collection.count - 1
let result = isCellVisible(section:"Your Section Number", row: lastCell)
every time I scroll up or down value get changed with wrong value at 1st it shows correct value but after scrolling it get changed every time.
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
//creating a cell using the custom class
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ShowPercentageAttandanceTableViewCell
cell.rollNo.text = String(describing: self.mainArrayRoll[indexPath.row])
var location = Int()
for item in self.rollPercentage {
if item.rollCall == indexPath.row {
location = item.absentCount
cell.percentage.text=String(location)
}
else if cell.percentage.text==("Label")
{
cell.percentage.text=String("0")
}
}
return cell
}
Here is the code.
[![Here in image 2 label is having text =2 and gray color label is having text 0 but it automatically get change after scroll as shown in image 2[![image 2 label changes after scroll but it is showing wrong][2]][2]
You are failing to set the cell.percentage.text to "0" when you didn't find the value you were looking for and you had a reused cell.
Try this:
if let item = self.rollPercentage.first(where: { $0.rollCall == indexPath.row }) {
location = item.absentCount
cell.percentage.text = String(location)
} else {
cell.percentage.text = "0"
}
My self.totalPriceLabel show's total price of all shop Product.It works fine but when is i scroll the cell the go off screen due to dequeueReusableCellWithIdentifier self.totalPriceLabel gets incorrect value.i am saving value in array which is stored in NSUserDefaults.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell : CartCell? = tableView.dequeueReusableCellWithIdentifier("cartCell") as! CartCell!
if(cell == nil)
{
cell = CartCell(style: UITableViewCellStyle.Default, reuseIdentifier: "cartCell")
}
cell?.itemCount.layer.cornerRadius = 5
cell?.clipsToBounds = true
cell?.itemCount.layer.borderWidth = 1
cell?.itemCount.layer.borderColor = UIColor.blackColor().CGColor
cell?.itemMinus.tag = indexPath.row
cell?.itemPlus.tag = indexPath.row
cell?.itemDelete.tag = indexPath.row
let key = self.readArray[indexPath.row]
cell?.itemCount.text = String("\(key.allValues[0])")
let tupleVar = getProductNameFromCharacter(String("\(key.allKeys[0])"))
cell?.itemName.text = tupleVar.tempName
cell?.itemPrice.text = String("\(tupleVar.price)")
//Actual Logic
let tempCount = key.allValues[0] as! Double
let nextItemPrice = (cell!.itemPrice.text! as NSString).doubleValue * tempCount
self.totalPriceLabel.text = String("\((self.totalPriceLabel.text! as NSString).doubleValue + nextItemPrice)")
return cell!
}
Issue: As scroll cell getting wrong values.for self.totalPriceLabel.
self.totalPriceLabel.text = String("((self.totalPriceLabel.text! as
NSString).doubleValue + nextItemPrice)")
How to get cell value which just goes out off screen ? how to fix this issue due to scrolling?
cellForRowAtIndexpath is the wrong place to do that calculation. You are assuming that iOS will call this function once for each cell. This isn't the case. This function is called to display a single cell. It could very well get called multiple times for the same cell as you scroll up and down.
You should be updating that total when you add or remove items from the underlying data (self.readArray).
Also, add code to change the total when the quantity button is tapped.
If you want more specific help, post the entire controller.
This is my first question so please do excuse me if I have broken guidelines.
I will post code down below and would appreciate any input. Easy technique is preferred.
So, to the question.
I have a custom cell and a label inside of it. I want the size of the label to change. I have already got a way for the table cell height to change but I haven't worked out how to change the size of the label.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("messageCell") as! MessageTableViewCell
let receiveTextSize: CGFloat = cell.receivedMessage.intrinsicContentSize().width
let sendTextSize: CGFloat = cell.sentMessage.intrinsicContentSize().width
cell.receivedMessage.alpha = 0
cell.sentMessage.alpha = 0
if messageReceived[indexPath.row] {
if receiveTextSize >= 220 {
cell.sentMessage.frame.size = CGSizeMake(cell.sentMessage.frame.width, 100)
}
cell.receivedMessage.text = messageContent[indexPath.row] as String
cell.receivedMessage.alpha = 1
} else {
if sendTextSize >= 220 {
cell.sentMessage.frame.size = CGSizeMake(cell.sentMessage.frame.width, 100)
cell.sentMessage.numberOfLines = 0
}
cell.sentMessage.text = messageContent[indexPath.row] as String
cell.sentMessage.alpha = 1
}
cell.receivedMessage.sizeToFit()
cell.receivedMessage.frame.size = CGSizeMake(100, 100)
return cell
}
Edit Solution was to set auto-constraints and then change the heightForRowAtIndexPath function for the tableView