I am trying to delete some rows in my UITableView by setting allowsMultipleSelectionDuringEditing to YES. This is all working well; the circle is showing on the left hand side.
However, for certain cells, I don't want the circle on the left hand side to come up. How do I do that? I've tried cell.selectionStyle = UITableViewCellSelectionStyleNone during editing and that didn't work.
Any hints?
In order to disallow some rows from multiple selection you should use tableView:shouldIndentWhileEditingRowAtIndexPath: mixed with cell.selectionStyle = UITableViewCellSelectionStyleNone.
Here is an example from my code:
- (BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath*)indexPath {
if (indexPath.row < 4) {
return YES;
} else {
return NO;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// (...) configure cell
if (indexPath.row < 4) {
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
} else {
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
}
First, use these settings:
self.tableView.allowsMultipleSelectionDuringEditing = true
self.tableView.setEditing(true, animated: false)
And implement next delegate methods:
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return self.shouldAllowSelectionAt(indexPath)
}
func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
return self.shouldAllowSelectionAt(indexPath)
}
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
if self.shouldAllowSelectionAt(indexPath) {
return indexPath
}
return nil
}
shouldAllowSelectionAt is my private method which contains logic about which row to select
Have you tried to implement tableView:editingStyleForRowAtIndexPath: UITableViewDelegate's method and return UITableViewCellEditingStyleNone for cells you don't want to show delete control?
Related
I would like to use a UITableView to have 2 static cells on top of a list of dynamic cells. As far as I understand, I have to use a dynamic prototype tableView. But I don't understand how to add 2 static cells and design them, eg. adding a textfield to the first and a label to the second.
What do I have to do in my storyboard? And what do I have to do inside the Controller? How can I differentiate the static from the dynamic cells?
EDIT:
I tried this for testing:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cardCell", forIndexPath: indexPath) as CardTableViewCell
//static cell
if (indexPath.row < 2) {
cell.dyn.text = "static \(indexPath.row)"
return cell;
}
// Configure the cell...
cell.dyn.text = "buh"
return cell
}
this results in this:
Later when I use real data I will miss the first 2 data rows...
Can I somehow "reset" the row counter after I created my static cells?
And how can I modify the 2 static cells? For adding a textfield and labels? Or do I have to do this programmatically?
I found help here: Mixing static and dynamic sections in a grouped table view
And my solution looks like this:
1.
Add and layout the static cells:
Give each cell a unique name and add them as outlet to the TableViewCell class
Adjust the code:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 3
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (section == 2){ // my dynamic cell is index 2
return 5 // just for testing, add here yourrealdata.count
}
return 1 // for static content return 1
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: CardTableViewCell!
if (indexPath.section == 0) {
cell = tableView.dequeueReusableCellWithIdentifier("static1", forIndexPath: indexPath) as CardTableViewCell
cell.cardSetName?.text = self.cardSetObject["name"] as String
}else if (indexPath.section == 1) {
cell = tableView.dequeueReusableCellWithIdentifier("static2", forIndexPath: indexPath) as CardTableViewCell // just return the cell without any changes to show whats designed in storyboard
}else if (indexPath.section == 2) {
cell = tableView.dequeueReusableCellWithIdentifier("cardCell", forIndexPath: indexPath) as CardTableViewCell
cell.dyn.text = "row \(indexPath.row)" // return test rows as set in numberOfRowsInSection
}
return cell;
}
End results will look like this:
I hope I can help someone with the same question :)
you could use something like this to use or display your static cell
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return numberOfDynamicCells + 1;
}
and in you cellForRowAtIndexPath datasource you may use something like this.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(indexPath.row == 0){
// go ahead to display your static Cell
}
else{
//go ahead to display your dynamic cells.
}
return yourCell;
}
here is code for swift.
func numberOfRowsInSection(_ section: Int) -> Int{
return numberOfDynamicCells + 1
}
and in you cellForRowAtIndexPath datasource you may use something like this.
func cellForRowAtIndexPath(_ indexPath: NSIndexPath) -> UITableViewCell?{
if indexPath.row = 0{
// go ahead to display your static Cell
}
else{
//go ahead to display your dynamic cells.
}
return yourCell;
}
Good Luck...
Yes you can by having static cells be IBOutlet properties and in the tableView(_:cellForRowAt:) you can return those properties for any index paths you want
Here's an example:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.row {
case 0:
return firstStaticCell
case 1:
return secondStaticCell
default:
let cell = tableView.dequeueReusableCell(withIdentifier: "DynamicCell", for: indexPath)
cell.textLabel?.text = "Dynamic \(indexPath.row + 1)"
return cell
}
}
I have a UITableView for which I have added some custom slide out buttons. These all work as expected. There is however a scenario in my table where a row can be in a state where none of the slide out buttons are relevant and so I have been returning an empty array of actions so the slide out buttons wont appear. Unfortunately once this occurs the UITableView stops calling my editActionsForRowAtIndexPath effectively disabling slide out buttons for all rows in my table ... and it seems permanent until the app is restarted.
Is this expected behaviour?
func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [AnyObject]?
{
if mydata[indexPath.row].EditAvailable()
{
var editAction = UITableViewRowAction(style: .Default, title: "Edit", handler: editHandler)
return [editAction]
}
else
{
return []
}
}
The way I solved this problem was to implement the function,
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool
Here, you should just return false if you don't want the expected row to have the swipe feature.
So you code would look something like this
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return mydata[indexPath.row].EditAvailable()
}
func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [AnyObject]?
{
var editAction = UITableViewRowAction(style: .Default, title: "Edit", handler: editHandler)
return [editAction]
}
The editActionsForRowAtIndexPath is then only called for the ones that you indicated are editable.
Please try to use tableView:editingStyleForRowAtIndexPath:.
You can control the on/off of the slide itself.
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
if (/* mydata[indexPath.row].EditAvailable() */) {
return UITableViewCellEditingStyleDelete;
} else {
return UITableViewCellEditingStyleNone;
}
}
(I tested in objective-c)
Avoid passing empty Arrays at -
tableView editActionsForRowAtIndexPath
Better do the condition check in -
tableView canEditRowAtIndexPath
Format your code like this :
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
if mydata[indexPath.row].EditAvailable() {
return true
}
return false
}
func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [AnyObject]?
{
var editAction = UITableViewRowAction(style: .Default, title: "Edit", handler: editHandler)
return [editAction]
}
First, editActionsForRowAtIndexPath should return nil and not [] when then are no actions.
func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [AnyObject]?
{
if mydata[indexPath.row].EditAvailable()
{
var editAction = UITableViewRowAction(style: .Default, title: "Edit", handler: editHandler)
return [editAction]
}
else
{
return nil
}
}
Second, you must implement editingStyleForRowAtIndexPath to disable the default "Delete" action for the other rows.
func tableView(tableView: UITableView, editingStyleForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCellEditingStyle {
if mydata[indexPath.row].EditAvailable() {
return UITableViewCellEditingStyle.Delete
}
return UITableViewCellEditingStyle.None
}
Thanks luciano for the tip empty array/nil tip
In iOS9.3, "canEditRowAtIndexPath" doesn't work!!!
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath;
So, the following code works for me. Only two methods should be implemented.
-(NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath {
if(//not editable){
return nil;
}
return #[//your actions];
}
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath{
if(//not editable){
return UITableViewCellEditingStyleNone;
}
return UITableViewCellEditingStyleDelete;
}
You should not return an empty array for the actions at editActionsForRowAtIndexPath. If you want to make the row not editable...
return false at canEditRowAtIndexPath. Once you return an empty array, all rows stop showing the swipe buttons. Its a strange behavior, i don't know whether its a glitch or as intended.
I implemented similar selective usage of actions for some tableCell using ob-c(have not tested in swift) a while ago but put the selectiveness in handleSwipe, below i have an action for all except the last one:
- (void)handleSwipeLeft:(UISwipeGestureRecognizer *)gestureRecognizer{
CGPoint location = [gestureRecognizer locationInView:self.tableView];
NSIndexPath *swipedIndexPath = [self.tableView indexPathForRowAtPoint:location];
if(!(swipedIndexPath.row<[coreDataHelper getAllTimerLists].count) ){
[self.tableView setEditing:!self.tableView.editing animated:YES];
}
}
Does this help you or do you need multiple different actions array, as opposed to this which only has one array and inactivates/activates it?
I am building an app that has a feed view for user-submitted posts. This view has a UITableView with a custom UITableViewCell implementation. Inside this cell, I have another UITableView for displaying comments. The gist is something like this:
Feed TableView
PostCell
Comments (TableView)
CommentCell
PostCell
Comments (TableView)
CommentCell
CommentCell
CommentCell
CommentCell
CommentCell
The initial feed will download with 3 comments for previewing, but if there are more comments, or if the user adds or deletes a comment, I want to update the PostCell in place inside of the feed table view by adding or removing CommentCells to the comments table inside of the PostCell. I am currently using the following helper to accomplish that:
// (PostCell.swift) Handle showing/hiding comments
func animateAddOrDeleteComments(startRow: Int, endRow: Int, operation: CellOperation) {
let table = self.superview?.superview as UITableView
// "table" is outer feed table
// self is the PostCell that is updating it's comments
// self.comments is UITableView for displaying comments inside of the PostCell
table.beginUpdates()
self.comments.beginUpdates()
// This function handles inserting/removing/reloading a range of comments
// so we build out an array of index paths for each row that needs updating
var indexPaths = [NSIndexPath]()
for var index = startRow; index <= endRow; index++ {
indexPaths.append(NSIndexPath(forRow: index, inSection: 0))
}
switch operation {
case .INSERT:
self.comments.insertRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None)
case .DELETE:
self.comments.deleteRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None)
case .RELOAD:
self.comments.reloadRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None)
}
self.comments.endUpdates()
table.endUpdates()
// trigger a call to updateConstraints so that we can update the height constraint
// of the comments table to fit all of the comments
self.setNeedsUpdateConstraints()
}
override func updateConstraints() {
super.updateConstraints()
self.commentsHeight.constant = self.comments.sizeThatFits(UILayoutFittingCompressedSize).height
}
This accomplishes the update just fine. The post is updated in place with comments added or removed inside of the PostCell as expected. I am using auto sizing PostCells in the feed table. The comments table of the PostCell expands to show all of the comments, but the animation is a bit jerky and the table sort of scrolls up and down a dozen pixels or so while the cell update animation takes place.
The jumping during resizing is a bit annoying, but my main issue comes afterwards. Now if I scroll down in the feed, the scrolling is smooth as before, but if I scroll up above the cell I just resized after adding comments, the feed will jump backwards a few times before it reaches the top of the feed. I setup iOS8 auto sizing cells for the Feed like this:
// (FeedController.swift)
// tableView is the feed table containing PostCells
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 560
If I remove the estimatedRowHeight, the table just scrolls to the top anytime a cell height changes. I'm feeling pretty stuck on this now and as a new iOS developer, could use any tips you might have.
Here is the best solution I found to solve this kind of problem (scrolling problem + reloadRows + iOS 8 UITableViewAutomaticDimension);
It consists by keeping every heights in a dictionary and updating them (in the dictionary) as the tableView will display the cell.
You will then return the saved height in - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath method.
You should implement something like this :
Objective-C
- (void)viewDidLoad {
[super viewDidLoad];
self.heightAtIndexPath = [NSMutableDictionary new];
self.tableView.rowHeight = UITableViewAutomaticDimension;
}
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
NSNumber *height = [self.heightAtIndexPath objectForKey:indexPath];
if(height) {
return height.floatValue;
} else {
return UITableViewAutomaticDimension;
}
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
NSNumber *height = #(cell.frame.size.height);
[self.heightAtIndexPath setObject:height forKey:indexPath];
}
Swift 3
#IBOutlet var tableView : UITableView?
var heightAtIndexPath = NSMutableDictionary()
override func viewDidLoad() {
super.viewDidLoad()
tableView?.rowHeight = UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if let height = heightAtIndexPath.object(forKey: indexPath) as? NSNumber {
return CGFloat(height.floatValue)
} else {
return UITableViewAutomaticDimension
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let height = NSNumber(value: Float(cell.frame.size.height))
heightAtIndexPath.setObject(height, forKey: indexPath as NSCopying)
}
We had the same problem. It comes from a bad estimation of the cell height that causes the SDK to force a bad height which will cause the jumping of cells when scrolling back up. Depending on how you built your cell, the best way to fix this is to implement the UITableViewDelegate method - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
As long as your estimation is pretty close to the real value of the cell height, this will almost cancel the jumping and jerkiness. Here's how we implemented it, you'll get the logic:
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
// This method will get your cell identifier based on your data
NSString *cellType = [self reuseIdentifierForIndexPath:indexPath];
if ([cellType isEqualToString:kFirstCellIdentifier])
return kFirstCellHeight;
else if ([cellType isEqualToString:kSecondCellIdentifier])
return kSecondCellHeight;
else if ([cellType isEqualToString:kThirdCellIdentifier])
return kThirdCellHeight;
else {
return UITableViewAutomaticDimension;
}
}
Added Swift 2 support
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
// This method will get your cell identifier based on your data
let cellType = reuseIdentifierForIndexPath(indexPath)
if cellType == kFirstCellIdentifier
return kFirstCellHeight
else if cellType == kSecondCellIdentifier
return kSecondCellHeight
else if cellType == kThirdCellIdentifier
return kThirdCellHeight
else
return UITableViewAutomaticDimension
}
dosdos answer worked for me in Swift 2
Declare the ivar
var heightAtIndexPath = NSMutableDictionary()
in func viewDidLoad()
func viewDidLoad() {
.... your code
self.tableView.rowHeight = UITableViewAutomaticDimension
}
Then add the following 2 methods:
override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let height = self.heightAtIndexPath.objectForKey(indexPath)
if ((height) != nil) {
return CGFloat(height!.floatValue)
} else {
return UITableViewAutomaticDimension
}
}
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
let height = cell.frame.size.height
self.heightAtIndexPath.setObject(height, forKey: indexPath)
}
SWIFT 3:
var heightAtIndexPath = [IndexPath: CGFloat]()
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return self.heightAtIndexPath[indexPath] ?? UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
self.heightAtIndexPath[indexPath] = cell.frame.size.height
}
#dosdos solution is working fine
but there is something you should added
following #dosdos answer
Swift 3/4
#IBOutlet var tableView : UITableView!
var heightAtIndexPath = NSMutableDictionary()
override func viewDidLoad() {
super.viewDidLoad()
tableView?.rowHeight = UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if let height = heightAtIndexPath.object(forKey: indexPath) as? NSNumber {
return CGFloat(height.floatValue)
} else {
return UITableViewAutomaticDimension
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let height = NSNumber(value: Float(cell.frame.size.height))
heightAtIndexPath.setObject(height, forKey: indexPath as NSCopying)
}
then use this lines when ever you want , for me I use it inside textDidChange
first reload Tableview
update constraint
finally move to top Tableview
tableView.reloadData()
self.tableView.layoutIfNeeded()
self.tableView.setContentOffset(CGPoint.zero, animated: true)
I was facing the same problem too. I did find a workaround, but it doesn't completely fix the jerk. But it seems to be a lot better compared to the previous choppy scrolling.
In your UITableView delegate method :cellForRowAtIndexPath:, try using the following two methods to update the constraints before returning the cell. (Swift language)
cell.setNeedsUpdateConstraints()
cell.updateConstraintsIfNeeded()
EDIT: You may also have to play around with the tableView.estimatedRowHeight value to get a smoother scrolling.
Following #dosdos answer.
I also found interesting to implement: tableView(tableView: didEndDisplayingCell: forRowAtIndexPath:
Specially for my code, where the cell is changing Constraints dynamically while the cell is already displayed on screen. Updating the Dictionary like this helps the second time the cell is displayed.
var heightAtIndexPath = [NSIndexPath : NSNumber]()
....
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = UITableViewAutomaticDimension
....
extension TableViewViewController: UITableViewDelegate {
//MARK: - UITableViewDelegate
func tableView(tableView: UITableView,
estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let height = heightAtIndexPath[indexPath]
if let height = height {
return CGFloat(height)
}
else {
return UITableViewAutomaticDimension
}
}
func tableView(tableView: UITableView,
willDisplayCell cell: UITableViewCell,
forRowAtIndexPath indexPath: NSIndexPath) {
let height: NSNumber = CGRectGetHeight(cell.frame)
heightAtIndexPath[indexPath] = height
}
func tableView(tableView: UITableView,
didEndDisplayingCell cell: UITableViewCell,
forRowAtIndexPath indexPath: NSIndexPath) {
let height: NSNumber = CGRectGetHeight(cell.frame)
heightAtIndexPath[indexPath] = height
}
}
I have a custom tableview cell in grouptableview. And I have one hidden. I then have to make it visible. Cell tag is 3.
This is not working my code:
if (self.tableView.tag == 3) {
self.tableView.hidden = NO; //Not working.
}
Just i need make a one row is visible. I hope you understand.
In SWIFT you need to do two things,
HIDE your cell. (because reusable cell may conflict)
Set Height of cell to ZERO.
Look at here,
HIDE you cell.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let myCell:UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cellID",for: indexPath) as! UITableViewCell
if(indexPath.row < 2){
myCell.isHidden = true
}else{
myCell.isHidden = false
}
return myCell
}
Set Height of cell to ZERO.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
var rowHeight:CGFloat = 0.0
if(indexPath.row < 2){
rowHeight = 0.0
}else{
rowHeight = 55.0 //or whatever you like
}
return rowHeight
}
Using this you can remove reusable cell conflict issues.
You can do the same for cell?.tag also to hide specific cell by tag.
Pass the cell height zero for that specific cell in the heightForRowAtIndexPath: , it will automatically get hidden:-
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
float heightForRow = 40;
YourCustomCell *cell =(YourCustomCell *)[tableView cellForRowAtIndexPath:indexPath];
if(cell.tag==3)
return 0;
else
return heightForRow;
}
Add the following method to your code , it will do the trick .
Hope it will help you .
please refer this code :-
- (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section
{
if(section == theSectionWithoutARow)
{
if(shouldRemoveTheRow)
return [theArrayWithTheSectionContents count] - 1;
else
return [theArrayWithTheSectionContents count];
}
// other sections, whatever
}
- (UITableViewCell *)tableView:(UITableView *)table cellForRowAtIndexPath: (NSIndexPath *)indexPath
{
// blah blah standard table cell creation
id theCellObject;
if(indexPath.section == theSectionWithoutARow)
{
NSInteger theActualRowToDisplay = indexPath.row;
if(shouldRemoveTheRow && indexPath.row >= theRowIndexToRemove)
{
theActualRowToDisplay = indexPath.row + 1;
}
theCellObject = [theArrayWithTheSectionContents objectAtIndex:theActualRowToDisplay];
}
// now set up the cell with theCellObject
return cell;
}
Hope this help you
Here is my scenario. First of all, my table view is static. And in section "Account", there should alway be only one cell displayed. LoggedInCell is displayed when user is logged in, and unLoggedInCell when user is not logged in.
One solution is to set their height zero, but you may encounter NSContraints error which is complex to be fixed.
My solution is below:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let count = super.tableView(tableView, numberOfRowsInSection: section)
if section == 0 {
return count - 1
}
return count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
if userForApp == nil {
return super.tableView(tableView, cellForRowAt: IndexPath(row: 0, section: 0))
} else {
return super.tableView(tableView, cellForRowAt: IndexPath(row: 1, section: 0))
}
} else {
return super.tableView(tableView, cellForRowAt: indexPath)
}
}
quite simple! yes?
Btw, You may have the cells height problem like me, I mean the two cells (UnLoggedInCell and LoggedInCell) have different height, we should tell the table view object that the value of cells height by doing this:
var originHeightOfUnLoggedInCell: CGFloat = 0.0
var originHeightOfLoggedInCell: CGFloat = 0.0
func recordInitialHeightOfCells() { // called in viewDidLoad()
self.originHeightOfUnLoggedInCell = self.unLoggedInCell.frame.height
self.originHeightOfLoggedInCell = self.loggedInCell.frame.height
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.section == 0 {
if userForApp == nil {
return originHeightOfUnLoggedInCell
} else {
return originHeightOfLoggedInCell
}
} else {
return super.tableView(tableView, heightForRowAt: indexPath)
}
}
I want something similar as the Alarm app, where you can't swipe delete the row, but you can still delete the row in Edit mode.
When commented out tableView:commitEditingStyle:forRowAtIndexPath:, I disabled the swipe to delete and still had Delete button in Edit mode, but what happens when I press the Delete button. What gets called?
Ok, it turns out to be quite easy. This is what I did to solve this:
Objective-C
- (UITableViewCellEditingStyle)tableView:(UITableView *)aTableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Detemine if it's in editing mode
if (self.tableView.editing)
{
return UITableViewCellEditingStyleDelete;
}
return UITableViewCellEditingStyleNone;
}
Swift 2
override func tableView(tableView: UITableView, editingStyleForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCellEditingStyle {
if tableView.editing {
return .Delete
}
return .None
}
Swift 3
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
if tableView.isEditing {
return .delete
}
return .none
}
You still need to implement tableView:commitEditingStyle:forRowAtIndexPath: to commit the deletion.
Just to make things clear, swipe-to-delete will not be enabled unless tableView:commitEditingStyle:forRowAtIndexPath: is implemented.
While I was in development, I didn't implement it, and therefore swipe-to-delete wasn't enabled. Of course, in a finished app, it would always be implemented, because otherwise there would be no editing.
You need to implement the CanEditRowAt function.
You can return .delete in the EditingStyleForRowAt function so you can still delete in editing mode.
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
if tableView.isEditing {
return true
}
return false
}
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
return .delete
}
Swift Version:
override func tableView(tableView: UITableView, editingStyleForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCellEditingStyle {
if(do something){
return UITableViewCellEditingStyle.Delete or UITableViewCellEditingStyle.Insert
}
return UITableViewCellEditingStyle.None
}
Basically, you enable or disable editing using the methods
- (void)setEditing:(BOOL)editing animated:(BOOL)animated
If editing is enabled, the red deletion icon appears, and a delete conformation requested to the user. If the user confirms, the delegate method
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
is notified of the delete request. If you implement this method, then swipe to delete is automatically made active. If you do not implement this method, then swipe to delete is not active, however you are not able to actually delete the row. Therefore, to the best of my knowledge, you can not achieve what you asked for, unless using some undocumented, private APIs. Probably this is how the Apple application is implemented.
On C#:
I had the same issue where it was needed to enable/disable rows with Delete option on swipe. Multiple rows needed to be swiped left and get deleted, keep them in another colour. I achieved using this logic.
[Export("tableView:canEditRowAtIndexPath:")]
public bool CanEditRow(UITableView tableView, NSIndexPath indexPath)
{
if (deletedIndexes.Contains(indexPath.Row)){
return false;
}
else{
return true;
}
}
Note that deletedIndexes are a list of indexes which are deleted from the table without duplicates. This code check whether a row is deleted, then it disables swipe or vice versa.
The equivalent delegate function is Swift is canEditRowAtIndexPath.
I came across this problem either and fixed with codes below. hope it will help you.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
BOOL deleteBySwipe = NO;
for (UIGestureRecognizer* g in tableView.gestureRecognizers) {
if (g.state == UIGestureRecognizerStateEnded) {
deleteBySwipe = YES;
break;
}
}
if (deleteBySwipe) {
//this gesture may cause delete unintendedly
return;
}
//do delete
}
}