How to prevent a UITableViewCell from moving (Swift 5) - swift

This is actually a two part question. First take a look at the code:
//canEditRowAt
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
if tableView.tag == 1000{
return indexPath.row == 0 ? false : true
}
return true
}
//canMoveRowAt
func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
if tableView.tag == 1000{
return indexPath.row == 0 ? false : true
}
return true
}
So from this I would expect that it would prevent the row at index 0 from having other cells move to it, but no... as you can see:
I obviously want to prevent it, but can't seem to find any documentation to solve the issue.
The other bit I'm struggling with; if you look at the recording you can see that once I move a cell into any location, a black bar appears behind the cell. I would like to avoid that from happening as well, and have tried several things but nothing has worked.
Any help is appreciated, thanks!

To answer the first question, if you look at the tableView(_:canMoveRowAt:) documentation :
This method allows the data source to specify that the reordering
control for the specified row not be shown. By default, the reordering
control is shown if the data source implements the
tableView(_:moveRowAt:to:) method.
This mainly talks about the reordering control being shown rather than specifically saying it can never be moved. So if you look at your UI, the reordering control is not showing for indexPath.row == 0
I can suggest 2 alternatives:
1. Reverse the move action
override func tableView(_ tableView: UITableView,
moveRowAt sourceIndexPath: IndexPath,
to destinationIndexPath: IndexPath)
{
// Reverse the action for the first row
if destinationIndexPath.row == 0
{
// You need the give a slight delay as the table view
// is still completing the first move animation
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5)
{
tableView.beginUpdates()
tableView.moveRow(at: destinationIndexPath,
to: sourceIndexPath)
tableView.endUpdates()
}
return
}
// update the data source
}
2. User a header view
This way you don't have to worry about specify any logic of which cells can move and which cannot as you can have the non movable data in a header view:
override func tableView(_ tableView: UITableView,
viewForHeaderInSection section: Int) -> UIView?
{
if section == 0
{
// Customize any view
let headerView = UIView(frame: CGRect(x: 0,
y: 0,
width: tableView.bounds.width,
height: 100))
headerView.backgroundColor = .red
let label = UILabel(frame: headerView.bounds)
label.text = "Header view"
label.textColor = .white
label.textAlignment = .center
headerView.addSubview(label)
return headerView
}
return nil
}
override func tableView(_ tableView: UITableView,
heightForHeaderInSection section: Int) -> CGFloat
{
if section == 0
{
// specify any height
return 100
}
return 0
}
I recommend the second option as it has the better user experience and seems to be the right way of approaching this problem.
To answer your second question, in your cellForRowAt indexPath or custom cell implementation, you probably set the background view of the cell or the contentView to black.
Try setting one of these or both:
cell.backgroundColor = .clear
cell.contentView.backgroundColor = .clear
This should not give you a black background

Implement tableView(_:targetIndexPathForMoveFromRowAt:toProposedIndexPath:) and ensure row 0 is never returned (return row 1 when proposedDestinationIndexPath is row 0).

Related

Add a label to a section of tableView when it's empty

Almost the same question many times has been asked here, but my question is a bit different, for example here, a users shows a really good way to handing an empty tableview with a label
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.numberOfRow == 0{
var emptyLabel = UILabel(frame: CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height))
emptyLabel.text = "No Data"
emptyLabel.textAlignment = NSTextAlignment.Center
self.tableView.backgroundView = emptyLabel
self.tableView.separatorStyle = UITableViewCellSeparatorStyle.None
return 0
} else {
return self.numberOfRow
}
}
It works fine when there is a section, but my problem is, I have two section that users can move cells between them and I want, when one of them become empty, a label appear in the section to say it's empty.
Could anyone modify this way to do that? also it should reload data to show this label, is it right?
Many thanks
The easiest way is to show a regular cell in the section if there are no other rows in the section.
Here's some rough pseudocode:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionData = data[section]
return sectionData.isEmpty ? 1 : sectionData.count
}
tableView(tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let sectionData = data[section]
if sectionData.isEmpty {
// create plain cell
cell.textLabel.text = "Nothing to see here"
return cell
} else {
// create and return your normal data cell from the data for the index path
}
}
Another option is to show a section header or footer for any section that has no rows.

When I scroll down in my TableView, the cell data changes - Swift 4

When I scroll down in my Table View, the cell data that has disappeared then changes. How can I solve this?
class TableViewController: UITableViewController {
var number = 1
let finishNumber = 10
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return finishNumber
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = "\(number)"
number = number + 1
return cell
}
}
You update number every time the table view asks for a cell. That has no direct relation to the row being displayed.
It's unclear why you even have the number property.
If you just want to show the corresponding row number in each cell, get rid of the number property and update:
cell.textLabel?.text = "\(number)"
with:
cell.textLabel?.text = "\(indexPath.row)"
Unclear question but I think you want to scroll to bottom when tableview reload right ?
extension UITableView {
func scrollToBottom(animated: Bool = false) {
let section = self.numberOfSections
if (section > 0) {
let row = self.numberOfRows(inSection: section - 1)
if (row > 0) {
self.scrollToRow(at: IndexPath(row: row - 1, section: section - 1), at: .bottom, animated: animated)
}
}
}
}
When calling, you need to use "async"
DispatchQueue.main.async(execute: {
yourTableView.reloadData()
yourTableView.scrollToBottom()
}
Good luck.

Dynamic cell size with resize cell animation

I have a table view with a dynamic height of cells. Above the table is menu with buttons. When I click on the menu button, the data is loaded into the table. When data is loaded, I want to have an animation in a cell that changes the height of a cell. I wonder how this can be done?
Thanks for the help.
Swift 4
Create a boolean variable in your model for checking if your cell is expanded or not.
if you want to expand the cell as long as your label height you should connect you labels constraint to your contentView in all directions and set number of lines to 0 in your storyboard.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
details[indexPath.row].cellIsOpen = !details[indexPath.row].cellIsOpen
detailTableView.reloadData()
detailTableView.beginUpdates()
detailTableView.endUpdates()
detailViewHeightConstraint.constant = CGFloat(detailTableView.contentSize.height) // the height of whole tableView
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if details[indexPath.row].cellIsOpen {
return UITableViewAutomaticDimension // or any number you wish
} else {
return 60 // default closed cell height
}
}
}
Also you can place this two lines in your viewDidLoad() function:
detailTableView.estimatedRowHeight = 60
detailTableView.rowHeight = UITableViewAutomaticDimension

Swift: How to animate the rowHeight of a UITableView?

I'm trying to animate the height of tableViewCell rows by calling startAnimation() inside the tableView function:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! TableViewCell
tableView.rowHeight = 44.0
startAnimation(tableView)
return cell
}
//MARK: Animation function
func startAnimation(tableView: UITableView) {
UIView.animateWithDuration(0.7, delay: 1.0, options: .CurveEaseOut, animations: {
tableView.rowHeight = 88.0
}, completion: { finished in
print("Row heights changed!")
})
}
The result: The row height does change but without any animation occurring. I don't understand why the animation doesn't work. Should I perhaps define some beginning and end state somewhere?
Don't change the height that way. Instead, when you know you want to change the height of a cell, call (in whatever function):
self.tableView.beginUpdates()
self.tableView.endUpdates()
These calls notify the tableView to check for height changes. Then implement the delegate override func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat, and provide the proper height for each cell. The change in height will be animated automatically. You can return UITableViewAutomaticDimension for items you don't have an explicit height for.
I would not suggest doing such actions from within cellForRowAtIndexPath, however, but in one that responds to a tap didSelectRowAtIndexPath, for example. In one of my classes, I do:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath == self.selectedIndexPath {
self.selectedIndexPath = nil
}else{
self.selectedIndexPath = indexPath
}
}
internal var selectedIndexPath: NSIndexPath? {
didSet{
//(own internal logic removed)
//these magical lines tell the tableview something's up, and it checks cell heights and animates changes
self.tableView.beginUpdates()
self.tableView.endUpdates()
}
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath == self.selectedIndexPath {
let size = //your custom size
return size
}else{
return UITableViewAutomaticDimension
}
}

How to set uitableviewcellselectionstyle.none to static cells?

Sorry I really cant find this anywhere.
I need to set my selection style to none so that the rows dont highlight when i click on it. Also, i need rows to be selectable as I have some rows which needs expanding and collapsing. I know the piece of code is UITableViewCellSelectionStyle.None but I have no idea where I can implement it. Thanks!
EDIT ADDED IN CODES
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 7
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
// Set height for date picker
if indexPath.section == 0 && indexPath.row == 2 {
let height:CGFloat = datePicker.hidden ? 0.0 : 216.0
return height
}
return super.tableView(tableView, heightForRowAtIndexPath: indexPath)
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// Expanding and collapsing date picker
UITableViewCellSelectionStyle.None
let datePickerIndexPath = NSIndexPath(forRow: 1, inSection: 0)
if datePickerIndexPath == indexPath {
datePicker.hidden = !datePicker.hidden
UIView.animateWithDuration(0.3, animations: { () -> Void in
self.tableView.beginUpdates()
// apple bug fix - some TV lines hide after animation
self.tableView.deselectRowAtIndexPath(indexPath, animated: true)
self.tableView.endUpdates()
})
}
}
The codes are mainly for the datepicker that i have implemented. everything works fine but clicking on the cell highlights the whole row in the default selection color.
Hard to know where to best implement it without seeing your code, but you can definitely put it in your cellForRowAtIndexPath when you dequeue/initialize your cell. Just call yourCell.selectionStyle = .None before return yourCell
cell.selectionStyle = .None
When you write the code after equal(=) , just press dot(.) so that many type of functionality will be pop up like this-
And for your second issue just put some value in array to check that is working correctly or not.
I have had the same issue, the solution for me was:
self.tableView.allowsSelection = true
in storyboard, manually select all the static cells and change selection from "default" to "none"
for those cells are allowed to be selected, change the selection to "Default".
It would appear as if only some cells can be selected.
You can still handle selection for those exclusive cells in didSelectRowAt by checking indexPath.
Hope that helps.
Just found a way to programmatically insert the selection style setting for static cells, instead of using storyboard:
In viewWillAppear, use:
tableView.cellForRow(at: yourIndexPath1)?.selectionStyle = .none
tableView.cellForRow(at: yourIndexPath1)?.selectionStyle = .none
...