CollectionView reloadItems(at: ) calls didSet() in multiple cells - ios

I'm working on an app that has a UICollectionView with an infinite scroll. Each cell has a like button. The problem is that the lower I scroll - the slower the like button changes its color when tapped on.
It can take up to 3 seconds between the print("BEGIN") and print("END") in my code. I also observed that print(post) gets called dozens of times and it prints dozens of posts that I haven't tapped the like button on. I want it to only call the didSet in the cell I'm updating.
Can someone please explain why it seems like it's calling didSet in multiple cells instead of just one cell I tapped on? Does it have something to do with that cells are being reused?
How would you fix this long delay problem? I would like the cell to update it's like button image to a selected one without such a long delay.
protocol HomePostCellDelegate {
func didTapLike(for cell: HomePostCell)
}
class HomePostCell: UICollectionViewCell {
var delegate: HomePostCellDelegate?
var post: Post? {
didSet {
print(post)
likeButton.setImage(post?.isLiked == true ? #imageLiteral(resourceName: "like_selected").withRenderingMode(.alwaysTemplate) : #imageLiteral(resourceName: "like_unselected").withRenderingMode(.alwaysTemplate), for: .normal)
likeButton.tintColor = .white
// ...
}
}
lazy var likeButton: UIButton = {
let button = UIButton(type: .system)
button.setImage(#imageLiteral(resourceName: "like_unselected").withRenderingMode(.alwaysOriginal), for: .normal)
button.addTarget(self, action: #selector(handleLike), for: .touchUpInside)
return button
}()
#objc func handleLike() {
NSLog("#objc func handleLike()")
delegate?.didTapLike(for: self)
}
// ...
}
class HomeController: UICollectionViewController, UICollectionViewDelegateFlowLayout, HomePostCellDelegate {
// ...
func didTapLike(for cell: HomePostCell) {
let generator = UIImpactFeedbackGenerator(style: .light)
generator.impactOccurred()
guard let indexPath = collectionView?.indexPath(for: cell) else { return }
guard let post = cell.post else { return }
cell.post.isLiked = !cell.post.isLiked
if cell.post.isLiked {
cell.post.numberOfLikes += 1
} else {
cell.post.numberOfLikes -= 1
}
print("BEGIN")
self.collectionView?.reloadItems(at: [indexPath])
print("END")
}
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.item == self.presenter.posts.count - 5 && !presenter.isFinishedPaging && !presenter.isCurrentlyPaging {
presenter.isCurrentlyPaging = true
presenter.paginatePosts(withSuccess: { [weak self] () in
self?.collectionView?.refreshControl?.endRefreshing()
self?.collectionView?.reloadData()
self?.presenter.isCurrentlyPaging = false
}) { [weak self] (errorMessage) in
self?.presenter.isCurrentlyPaging = false
print(errorMessage)
}
}
if presenter.posts.count == 0 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: errorCellId, for: indexPath) as! ErrorCell
setUpErrorCellAnimation(cell: cell)
return cell
} else if presenter.posts[indexPath.item].id == nil {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: adCellId, for: indexPath) as! AdCell
// some ad logic here
// ...
cell.contentView.addSubview(bannerView)
return cell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! HomePostCell
cell.delegate = self
if presenter.isAdmin && presenter.isInAdminView {
cell.isInAdminView = true
} else {
cell.isInAdminView = false
}
cell.post = presenter.posts[safe: indexPath.item]
return cell
}
}
UPDATE:
Stack trace when putting a breakpoint inside the didSet method:

Related

How to make only one button clickable in table view cell using swift

I have a table view with buttons in each of the cell. Each of the button playing different song for each of the cell and change image to "play" or "pause". But I have a problem, when I tap on two or three of buttons, they changes photo to "pause". It should change photo only on one of them. Check photo: Buttons in cell
There is my code in view controller:
extension BeatPackViewController: UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 12
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: CustomLoopsCell = beatTableView.dequeueReusableCell(withIdentifier: "firstLoopCell", for: indexPath) as! CustomLoopsCell
gettingSongName()
cell.loopNameLabel.text = data[indexPath.row].loop_name
cell.producerLabel.text = data[indexPath.row].producer
cell.instrumentLabel.text = data[indexPath.row].Instrument
cell.delegate = self
cell.selectionStyle = .none
cell.tag = indexPath.row
if let playingCell = currentPlayingIndex {
if playingCell == indexPath.row {
cell.playButtonOutlet.setImage(UIImage(named: "Pause.png"), for:
.normal)
}
} else {
cell.playButtonOutlet.setImage(UIImage(named: "playBtn.png"), for:
.normal)
}
// cell.instrumentLabel.text = data[indexPath.row].loops[indexPath.row].Instrument
// cell.producerLabel.text = data[indexPath.row].loops[indexPath.row].producer
return cell
}
func btnUseTap(cell: CustomLoopsCell) {
let indexPath = self.beatTableView.indexPath(for: cell)
if currentPlayingIndex == cell.tag {
audioPlayer.pause()
currentPlayingIndex = nil
beatTableView.reloadData()
} else { //IF PAUSE BUTTON
playLoop(song_name: songs[cell.tag])
currentPlayingIndex = cell.tag
beatTableView.reloadData()
}
beatTableView.reloadData()
// playSong(index: indexPath!.row)
print("Done")
}
1)Firstly create empty array of your buttons, for example:
let allButtons: [UIButton] = []
2)When you are creating each cell, add button of that cell to array , Example code:
allButtons.append(yourButton)
3)create function that will mute all buttons and also assigning pause image to them, for example:
func muteAllButtons() {
for button in allButtons {
button.muteThisButton()
button.setImageToPlay()
}
}
create function that will handle muting all buttons, and then playing music from selected button, for example:
func userSelectedButton(at yourSelectedCellIndex: Int) {
muteAllButtons()
let currentPlayingButton = allButtons[yourSelectedCellIndex]
currentPlayingButton.playMusic()
currentPlayingButton.setImageToPause()
}
when user clicks on selected cell, call userSelected function. For example:
userSelectedButton(at: yourCellIndex)
Looks like you have problem in your if:
if let playingCell = currentPlayingIndex {
if playingCell == indexPath.row {
cell.playButtonOutlet.setImage(UIImage(named: "Pause.png"), for:
.normal)
}
} else {
cell.playButtonOutlet.setImage(UIImage(named: "playBtn.png"), for:
.normal)
}
When currentPlayingIndex != nil and playingCell != indexPath.row, it doesn't update the image, so it gets random image from dequeued cell. Change it to:
if let playingCell = currentPlayingIndex, playingCell == indexPath.row {
cell.playButtonOutlet.setImage(UIImage(named: "Pause.png"), for:
.normal)
} else {
cell.playButtonOutlet.setImage(UIImage(named: "playBtn.png"), for:
.normal)
}
Also you have redundant reloadDatas here:
if currentPlayingIndex == cell.tag {
audioPlayer.pause()
currentPlayingIndex = nil
beatTableView.reloadData()
} else { //IF PAUSE BUTTON
playLoop(song_name: songs[cell.tag])
currentPlayingIndex = cell.tag
beatTableView.reloadData()
}
beatTableView.reloadData()
Just remove both from if/else and left one after the if/else.

Table View Data is overridden

I have a UITableView. Its cell contains a label that will display a question, a yes button and a no button. The goal is to view questions one by one.
First I call the API to get the questions in the viewDidLoad method:
override func viewDidLoad() {
super.viewDidLoad()
tableView.allowsSelection = false
getQuestions(baseComplainID: "1") { (questions, error) in
self.questions = questions
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
In the cellForRowAt method I display them one by one:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? TableViewCell else {
fatalError("Fatal Error")
}
cell.yesButton.isHidden = false
cell.noButton.isHidden = false
if indexPath.row + 1 == displayNumber {
cell.questionLabel.text = questions[indexPath.row].question_name
} else {
cell.yesButton.isHidden = true
cell.noButton.isHidden = true
}
cell.yesButton.addTarget(self, action: #selector(action), for: .touchUpInside)
cell.noButton.addTarget(self, action: #selector(action), for: .touchUpInside)
return cell
}
and this is the action being executed on clicking yes or no:
#objc func action(sender: UIButton){
let indexPath = self.tableView.indexPathForRow(at: sender.convert(CGPoint.zero, to: self.tableView))
let cell = tableView.cellForRow(at: indexPath!) as? TableViewCell
cell?.yesButton.isEnabled = false
cell?.noButton.isEnabled = false
if sender == cell?.yesButton {
sender.setTitleColor(.black, for: .normal)
sender.backgroundColor = .green
} else {
sender.setTitleColor(.black, for: .normal)
sender.backgroundColor = .green
}
displayNumber += 1
self.tableView.reloadData()
}
Here I just change the background color of the button and increment the display number to display the next question.
All of this works perfect EXCEPT when I scroll, the data gets overridden and sometimes I find the question label empty and the questions replaces each other. I know this is normal due to the cell reusability but I don't know how to fix it.
Any suggestions please?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? TableViewCell else {
fatalError("Fatal Error")
}
cell.yesButton.isHidden = false
cell.noButton.isHidden = false
if indexPath.row + 1 == displayNumber {
cell.questionLabel.text = questions[indexPath.row].question_name
} else {
cell.yesButton.isHidden = true
cell.noButton.isHidden = true
}
cell.yesButton.addTarget(self, action: #selector(action), for: .touchUpInside)
cell.noButton.addTarget(self, action: #selector(action), for: .touchUpInside)
return cell
}
i feel like your issue lies here in cellForRowAt function.
you have this written
if indexPath.row + 1 == displayNumber { your code here }
but i am unsure as to why you need this.
you should be doing something like this inside cellForRowAt
let data = self.questions
data = data[indexPath.row]
cell.questionLabel.text = data.question_name
you should not be adding 1 to your indexPath.row
You're going to need to keep track of your yes's no's and neither's for each cell. I'd tack an enum onto another data structure along with your questions. Your primary problem was that you were only keeping track of your question. You need to keep track of your answer as well. That way, when you load a cell, you can configure each button with the colors that you want in cellForRow(at:)
struct QuestionAndAnswer {
enum Answer {
case yes
case no
case nada
}
var question: Question
var answer: Answer
}
And try not to reload your whole tableView when a button is pressed. tableView.reloadData() is expensive and distracting to the user. You should only be reloading the row that changed when a button was pressed.
Add callbacks on your cell so that you know which cell the corresponding buttons belong to. Notice how in the onYes and onNo callbacks we keep track of your "yes" or "no" selection then immediately reload the row below. When the row is reloaded, we finally know which color to make the button.
class AnswerCell: UITableViewCell {
#IBOutlet weak var yesButton: UIButton!
#IBOutlet weak var noButton: UIButton!
var onYes: (() -> Void)) = {}
var onNo: (() -> Void)) = {}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// ...
cell.yesButton.backgroundColor = qAndA.answer == .yes ? .green : .white
cell.noButton.backgroundColor = qAndA.answer == .no ? .green : .white
cell.onYes = {
questionsAndAnswers[indexPath.row].answer = .yes
tableView.reloadRows(at: [indexPath], with: .fade)
}
cell.onNo = {
questionsAndAnswers[indexPath.row].answer = .no
tableView.reloadRows(at: [indexPath], with: .fade)
}
// ...
}
Well, assume you have 10 questions, so a very simple and workaround fix is to declare a new array which has 10 elements as follow
var questionIsLoaded = Array(repeating:true , count 10)
the previous line will declare an array with 10 elements each element is bool which in our case will be true
then declare a function that handles if the question is loaded or not as follows, so if the question is loaded thus, the question with its indexPath should be marked as true and as a result, the yes and no buttons should be hidden else, the buttons should be shown
func handleQuestionIfLoaded(cell:yourCellType, indexPath:IndexPath) {
if questionIsLoaded[indexPath.row] , indexPath.row + 1 == displayNumber { {
questionIsLoaded[indexPath.row] = false
cell.questionLabel.text = questions[indexPath.row].question_name
cell.yesButton.isHidden = questionIsLoaded[indexPath.row]
cell.noButton.isHidden = questionIsLoaded[indexPath.row]
} else {
cell.yesButton.isHidden = questionIsLoaded[indexPath.row]
cell.noButton.isHidden = questionIsLoaded[indexPath.row]
}
cell.yesButton.addTarget(self, action: #selector(action), for: .touchUpInside)
cell.noButton.addTarget(self, action: #selector(action), for: .touchUpInside)
}
then replace the body of cellForRowAt with the function above, then your action function will be as follows
#objc func action(sender: UIButton){
let indexPath = self.tableView.indexPathForRow(at: sender.convert(CGPoint.zero, to: self.tableView))
let cell = tableView.cellForRow(at: indexPath!) as? TableViewCell
cell?.yesButton.isEnabled = questionIsLoaded[indexPath.row]
cell?.noButton.isEnabled = questionIsLoaded[indexPath.row]
if sender == cell?.yesButton {
sender.setTitleColor(.black, for: .normal)
sender.backgroundColor = .green
} else {
sender.setTitleColor(.black, for: .normal)
sender.backgroundColor = .green
}
displayNumber += 1
self.tableView.reloadData()
}
Now, your cells depend on an external dependency which is the array you have declared earlier, this means that when the cells are dequeued, they will be reused according to if the question is loaded or not by asking the array's element at the specific indexPath at first if the element is true or false

UISearchController takes the wrong item

this is my code here I touch on + before search It gives correct product. but the problem is after search, it gives previous product not the correct product
after searching UISearchController looks up self.tableView
search is working finely
table view
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Configure the cell...
// Table view cells are reused and should be dequeued using a cell identifier.
let cellIdentifier = "MasterViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? MasterViewCell else {
fatalError("The dequeued cell is not an instance of OutletViewCell.")
}
// Fetches the appropriate meal for the data source layout.
var product = products[indexPath.row]
if isFiltering {
product = filteredProducts[indexPath.row]
} else {
product = products[indexPath.row]
}
cell.productName.text = product.productDescription
cell.availQty.text = "Avail. Qty:" + String(product.stock)
cell.productPrice.text = "Price: " + String(product.defaultSellPrice)
cell.addItem.tag = indexPath.row
cell.addItem.addTarget(self, action: #selector(buttonTapped(button:)), for: .touchUpInside)
orderDetails.forEach { detail in
// print(word.price)
if detail.key == product.id {
cell.AddedQty.text = "Qty :" + String(detail.value.qty)
}
}
return cell
}
here is buttonTapped function
#objc func buttonTapped(button: UIButton) {
// print("Button pressed " + String(button.tag))
let product=products[button.tag]
print(product.productDescription)
showAlert(product: product)
}
In your buttonTapped function, you also need to check if the data isFiltered or not
#objc func buttonTapped(button: UIButton) {
let productData = isFiltering ? filteredProducts[indexPath.row] : products[indexPath.row]
showAlert(product: productData)
}

Issue with multiple selection and select all in UITableview in swift

In my application I have an multiple selection and select all option, I've tried by used below code but am facing an issue with selection.
If I press selectAll it checks and unchecks clearly and If suppose, I choose an single selection it selected but the selected cell is not higlighted.
Can you any one help me to figure out.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.array.count
}
In cellForRowAtIndexPath ,
let cell = self.multipleSelectionTableview.dequeueReusableCell(withIdentifier:multiSelectionIdentifier , for: indexPath) as! MultipleSelectionCell
if self.selectedRows.contains(indexPath){
cell.checkBoxBtn.isSelected = true
cell.checkBoxBtn.setImage(UIImage.init(named: "check"), for: .normal)
} else {
cell.checkBoxBtn.isSelected = false
cell.checkBoxBtn.setImage(UIImage.init(named: "uncheck"), for: .normal)
}
if self.multipleSelectionStatusBtn.isSelected == true {
self.multipleSelectionStatusBtn.isSelected = true
cell.checkBoxBtn.setImage(UIImage.init(named: "check"), for: .normal)
} else {
self.multipleSelectionStatusBtn.isSelected = false
cell.checkBoxBtn.setImage(UIImage.init(named: "uncheck"), for: .normal)
}
cell.checkBoxBtn.tag = indexPath.row
return cell
In checkbox selection method,
let selectedIndexPath = IndexPath(row: sender.tag, section: 0)
self.multipleSelectionTableview.deselectRow(at: selectedIndexPath, animated: true)
if self.selectedRows.contains(selectedIndexPath)
{
self.selectedRows.remove(at: self.selectedRows.index(of: selectedIndexPath)!)
}
else
{
self.selectedRows.append(selectedIndexPath)
print(self.selectedRows)
}
self.multipleSelectionTableview.reloadData()
In selectAll method,
if (sender.isSelected == true)
{
sender.setImage(UIImage(named: "uncheck"), for: .normal)
sender.isSelected = false;
// isSelecting = false
self.selectedRows = self.getAllIndexpaths()
self.multipleSelectionTableview.reloadData()
}
else
{
// isSelecting = true
sender.setImage(UIImage(named: "check"), for: .normal)
sender.isSelected = true;
self.selectedRows = self.getAllIndexpaths()
self.multipleSelectionTableview.reloadData()
}
For getAllIndexPaths Method,
func getAllIndexpaths() -> [IndexPath] {
var indexPaths: [IndexPath] = []
for j in 0..<self.multipleSelectionTableview.numberOfRows(inSection: 0) {
indexPaths.append(IndexPath(row: j, section: 0))
}
return indexPaths
}
Actually you are reloading data when check box is checked or unchecked. That's the reason the table view doesn't keep the selection.
Solution.
You need to keep track of the selected index paths. And in cellForRowAt: you need to check that if it is the checked index path the you need to make cell highlight by changing its background colour otherwise set cell background colour white or something else.
For eg.
//In cellForRowAtIndexPath
if arrSelectedIndexPath.contains(indexPath) {
// checkbox is checked, change cell bg color
} else {
// checkbox is not checked
}
arrSelectedIndexPath is an [IndexPath] an array of index paths. When check box is selected you need to insert index path in an array and when check box is unchecked then you need to delete that index path from an array.
I am confused with your logic implementation in cellForRowAtIndexpath:
First you are checking if selectedRows array contains selected index path or not and setting the UI of checkBoxBtn:
let cell = self.multipleSelectionTableview.dequeueReusableCell(withIdentifier:multiSelectionIdentifier , for: indexPath) as! MultipleSelectionCell
if self.selectedRows.contains(indexPath){
cell.checkBoxBtn.isSelected = true
cell.checkBoxBtn.setImage(UIImage.init(named: "check"), for: .normal)
} else {
cell.checkBoxBtn.isSelected = false
cell.checkBoxBtn.setImage(UIImage.init(named: "uncheck[enter image description here][1]"), for: .normal)
}
Then again you are checking for multipleSelectionStatusBtn and changing the UI of checkBoxBtn :
if self.multipleSelectionStatusBtn.isSelected == true {
self.multipleSelectionStatusBtn.isSelected = true
cell.checkBoxBtn.setImage(UIImage.init(named: "check_timesheet"), for: .normal)
} else {
self.multipleSelectionStatusBtn.isSelected = false
cell.checkBoxBtn.setImage(UIImage.init(named: "uncheck_timesheet"), for: .normal)
}
cell.checkBoxBtn.tag = indexPath.row
return cell
When tableView will reload obviously this will work in multiple selection and not in single selection.
Change
In selectAll method :
if (sender.isSelected == true)
{
sender.setImage(UIImage(named: "uncheck"), for: .normal)
sender.isSelected = false;
// isSelecting = false
self.selectedRows.removeAll()
self.multipleSelectionTableview.reloadData()
}
else
{
// isSelecting = true
sender.setImage(UIImage(named: "check"), for: .normal)
sender.isSelected = true;
self.selectedRows = self.getAllIndexpaths()
self.multipleSelectionTableview.reloadData()
}
In cellForRowAtIndexPath :
let cell = self.multipleSelectionTableview.dequeueReusableCell(withIdentifier:multiSelectionIdentifier , for: indexPath) as! MultipleSelectionCell
if self.selectedRows.contains(indexPath){
cell.checkBoxBtn.isSelected = true
cell.checkBoxBtn.setImage(UIImage.init(named: "check"), for: .normal)
} else {
cell.checkBoxBtn.isSelected = false
cell.checkBoxBtn.setImage(UIImage.init(named: "uncheck[enter image description here][1]"), for: .normal)
}
if getTimeSheetJSON[indexPath.row]["TaskName"].string == "" {
cell.checkBoxBtn.isHidden = true
} else {
cell.checkBoxBtn.isHidden = false
}
cell.checkBoxBtn.tag = indexPath.row
return cell
Hope this works as I haven't compiled your code in Xcode.
You have to store selected index path in Array Like this Way you can do this easily.
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var selectedRow:[IndexPath] = []
#IBOutlet var tblView:UITableView!
override func viewDidLoad()
{
super.viewDidLoad()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section:
Int) -> Int
{
return 100
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
var cell = tableView.dequeueReusableCell(withIdentifier: "Cell")
if cell == nil
{
cell = UITableViewCell(style: .subtitle, reuseIdentifier: "Cell")
}
cell?.textLabel?.text = String(format:"%#",indexPath.row)
if selectedRow.contains(indexPath)
{
cell?.accessoryType = .checkmark
}
else
{
cell?.accessoryType = .none
}
return cell!
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
if selectedRow.contains(indexPath)
{
// For Remove Row selection
selectedRow.remove(at: selectedRow.index(of: indexPath)!)
}
else
{
selectedRow.append(indexPath)
}
tableView.reloadData()
}
// Call this method for Select All
func selectAllCall()
{
var indexPaths: [IndexPath] = []
for j in 0..<100
{
indexPaths.append(IndexPath(row: j, section: 0))
}
selectedRow.removeAll()
selectedRow.append(contentsOf: indexPaths)
tblView.reloadData()
}
}

Only Single cell expands at a time in UITableViewCell in swift3

I am implementing an expandable and collapsable table cell on the click of a button in the custom table cell. I have tried the following code but it expands only single cell at a time. That is if I click a cell it expands but if I click on another cell it expands and the already expanded cell collapses.
var selectedIndexPath: IndexPath?
func configure(cell: MyProposalCustomCell, forRowAtIndexPath indexPath: IndexPath) {
let pool = myProposalsDetails[indexPath.row]
cell.preservesSuperviewLayoutMargins = false
cell.separatorInset = UIEdgeInsets.zero
cell.layoutMargins = UIEdgeInsets.zero
cell.proposalNumber.text = pool.proposalNumber
cell.pickUpLocation.text = pool.pickupLocation
cell.dropLocation.text = pool.dropLocation
cell.journeyType.text = pool.journeyType
cell.firstShiftOnwardTime.text = pool.firstPickupTime
cell.firstShiftReturnTime.text = pool.firstEndTime
if let numberOfInterests = pool.noOfInterest {
cell.numberOfInterest.text = String(numberOfInterests)
}
if let operatingDays = pool.operatingDays {
cell.daysOfOperation.attributedText = Utility.getProposedOperatingDays(operatingDays: operatingDays)
}
cell.expandCollapse.tag = indexPath.row
cell.expandCollapse.addTarget(self, action: #selector(expandTableCell(sender:)), for: .touchUpInside)
if selectedIndexPath == indexPath {
UIView.animate(withDuration: 0.3, animations: {
cell.backgroundColor = CustomColor.selectedBackground.color
cell.bottomView.backgroundColor = CustomColor.selectedBackground.color
cell.expandCollapse.setImage(UIImage(named: "collapse_arrow.png"), for: .normal)
if let proposedStartDate = pool.startDate {
let propStartDate = Date(timeIntervalSince1970: proposedStartDate)
cell.proposedStartDate.text = Utility.getFormattedDate(date: propStartDate)
cell.proposedStartDateTxt.text = NSLocalizedString("Proposed start date", comment: "")
}
cell.returnTime.alpha = 0.0
})
} else {
UIView.animate(withDuration: 0.3, animations: {
cell.backgroundColor = UIColor.white
cell.expandCollapse.setImage(UIImage(named: "down_arrow.png"), for: .normal)
cell.proposedStartDateTxt.text = NSLocalizedString("Journey type", comment: "")
cell.bottomView.backgroundColor = UIColor.white
cell.proposedStartDate.text = pool.journeyType
cell.returnTime.isHidden = false
})
}
}
This is the expandable button action:
func expandTableCell(sender: UIButton) {
let indexPath = IndexPath(row: sender.tag, section: 0)
if selectedIndexPath == indexPath {
selectedIndexPath = nil
} else {
let previousSelIndex = selectedIndexPath
selectedIndexPath = indexPath
if let previousSelectedIndexPath = previousSelIndex {
if tripStatus.tripType != .splitShift {
if let previousSelectedCell = myProposals.cellForRow(at: previousSelectedIndexPath) as? MyProposalCustomCell {
configure(cell: previousSelectedCell, forRowAtIndexPath: previousSelectedIndexPath)
}
} else {
if let previousSelectedCell = myProposals.cellForRow(at: previousSelectedIndexPath) as? MyProposalSplitShiftCell {
configureSplitShift(cell: previousSelectedCell, forRowAtIndexPath: previousSelectedIndexPath)
}
}
}
}
updateSelectedCell(indexPath: indexPath)
myProposals.beginUpdates()
myProposals.endUpdates()
}
func updateSelectedCell(indexPath: IndexPath) {
if tripStatus.tripType != .splitShift {
if let selectedCell = myProposals.cellForRow(at: indexPath) as? MyProposalCustomCell {
configure(cell: selectedCell, forRowAtIndexPath: indexPath)
}
} else {
if let selectedCell = myProposals.cellForRow(at: indexPath) as? MyProposalSplitShiftCell {
configureSplitShift(cell: selectedCell, forRowAtIndexPath: indexPath)
}
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let headingHeight: CGFloat = 128
let detailsHeight: CGFloat = 80
let splitShiftDetailsHeight: CGFloat = 215
switch tripStatus.tripType {
case .oneWayTrip, .roundTrip:
if selectedIndexPath != nil && indexPath.compare(selectedIndexPath! as IndexPath) == ComparisonResult.orderedSame {
return headingHeight + detailsHeight
}
return headingHeight
case .splitShift:
if selectedIndexPath != nil && indexPath.compare(selectedIndexPath! as IndexPath) == ComparisonResult.orderedSame {
return headingHeight + splitShiftDetailsHeight
}
return headingHeight
}
}
I want to get multiple cells to be expanded. How to achieve this?
This is very straight forward, instead of taking selectedIndexPath try to keep one variable in your custom UITableViewCell class something like,
class ExpandableCell: UITableViewCell {
var isExpanded: Bool = false
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
#IBAction func moreButtonTapped(_ sender: Any) {
}
}
And then try to toggle the isExpanded boolean flag, and try to perform your func updateSelectedCell(indexPath: IndexPath) {} action.

Resources