Swift: Swipe action to strikethrough row in TableView - ios

Evening ladies and gentleman,
I am currently getting used to Swift and wanted to start with a little todo app. So far I can add an item and safe it persistently in a context. When an item has been added, it will be shown in a tableview. Now, I want to use a check swipe to strikethrough items, which have been added and safe this information in my context. Deleting using a swipe works perfectly fine.
Has anybody an idea how realize this? I tried to solve it by myself, but couldnt get it done. A similar question has been asked here before, but didnt get a proper answer: Add strikethrough to tableview row with a swipe
func checkAccessoryType(cell: UITableViewCell, isCompleted: Bool) {
if isCompleted {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let todo = CoreDataManager.shared.getTodoItem(index: indexPath.row)
todo.completed = !todo.completed
CoreDataManager.shared.safeContext()
if let cell = tableView.cellForRow(at: indexPath){
checkAccessoryType(cell: cell, isCompleted: todo.completed)
}
}

Assuming you are trying to strikethrough the title of your task -- which should be defined as a label -- here is the approach to take:
1- Make sure your label is set to attributed text rather than plain. To do that, go to Main.storyboard, select your label, and inside the attribute inspector, set text to Attributed.
2- Inside your completion block (that is the completion block executed after a swipe) add the following code:
(SWIFT 5)
let attributeString: NSMutableAttributedString = NSMutableAttributedString(string: taskLabel.text)
attributeString.addAttribute(.strikethroughStyle, value: 1, range: NSRange(location: 0, length: taskLabel.text.count))
taskLabel.attributedText = attributeString
Just a little advice: it's always helpful if you add some code when you ask a question.
Let me know if anything doesn't make sense.

Looking at the link that you provided, you need swipe action on your UITableViewCell.
Try looking into:
leadingSwipeActionsConfigurationForRowAt
trailingSwipeActionsConfigurationForRowAt
You need this action to perform the strikethrough label or delete:
func tableView(_ tableView: UITableView,
leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration?
{
let closeAction = UIContextualAction(style: .normal, title: "Close", handler: { (ac:UIContextualAction, view:UIView, success:(Bool) -> Void) in
print("OK, marked as Closed")
success(true)
})
closeAction.image = UIImage(named: "tick")
closeAction.backgroundColor = .purple
return UISwipeActionsConfiguration(actions: [closeAction])
}
func tableView(_ tableView: UITableView,
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration?
{
let modifyAction = UIContextualAction(style: .normal, title: "Update", handler: { (ac:UIContextualAction, view:UIView, success:(Bool) -> Void) in
print("Update action ...")
success(true)
})
modifyAction.image = UIImage(named: "hammer")
modifyAction.backgroundColor = .blue
return UISwipeActionsConfiguration(actions: [modifyAction])
}
Source: https://developerslogblog.wordpress.com/2017/06/28/ios-11-swipe-leftright-in-uitableviewcell/

Related

UITableView Destructive Context Menu Animation

I'm adding iOS13 context menus to my table view. One of the menu actions allows the user to delete the item:
override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { suggestedActions in
let deleteAction = UIAction(title: "Delete", image: UIImage(systemName: "trash.fill"), identifier: nil, discoverabilityTitle: "", attributes: UIMenuElement.Attributes.destructive) { action in
self.data.remove(at: indexPath.row)
//Remove from the table.
self.tableView.deleteRows(at: [indexPath], with: .automatic)
}
return UIMenu(title: "", children: [deleteAction])
}
}
I'm using the default preview view controller (so it just shows the cell). I'm currently seeing a weird animation artifact where the context menu preview is displayed while the items below the row being removed animated up, then the preview fades away to white (so it looks like there is a blank row in the list), then the table repaints and displays the item that was covered up.
This is using the default cell but it looks a lot worse when using a customized cell with a lot more information. Is there anyway to make this action animate better?
I ran into this problem as well. The bug is probably due to the fact that the original cell used to generate the preview was deleted, moved or changed.
The solution I found was to implement the delegate method tableView(_:previewForHighlightingContextMenuWithConfiguration:), passing it the original cell as the view, but customising UIPreviewParameters to use UIColor.clear:
override func tableView(_ tableView: UITableView, previewForHighlightingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
guard let indexPath = configuration.identifier as? IndexPath, let cell = tableView.cellForRow(at: indexPath) else {
return nil
}
let parameters = UIPreviewParameters()
parameters.backgroundColor = .clear
return UITargetedPreview(view: cell, parameters: parameters)
}
In order to identify the original cell in this delegate method, you'd need a way to identify it. One way is to set the indexPath as the identifier to UIContextMenuConfiguration, like:
override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
return UIContextMenuConfiguration(identifier: indexPath as NSIndexPath, previewProvider: nil) { _ in
return UIMenu(title: "", children: [
UIAction(title: "Delete", image: UIImage(systemName: "trash"), attributes: .destructive) { action in
self.data.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
}
])
}
}
However if your data can change between the presentation of the context menu and the action, then you need a more robust way to identify it.
I did not have to implement tableView(_:previewForDismissingContextMenuWithConfiguration:) for this to work.

How to give different tableViews same action between ViewControllers [swift]

I have an app with multiple view controllers with a tableview in each of them. Each view controller's table have their own specific purpose, but I use the same swipe actions in each table. So far I have just been copying and pasting the same code for trailingSwipeActionsConfigurationForRowAt and leadingSwipeActionsConfigurationForRowAt in each table. This feels wrong and I know there should be a way where I can write the code once and use it throughout the app but I am not sure how to do this.
I have tried to extensions of UITableView but can not seem to use the trailingSwipeActionsConfigurationForRowAt or leadingSwipeActionsConfigurationForRowAt functions in the extension file. What am I missing?
Here's the code for trailingSwipeActionsConfigurationForRowAt which is just swipe to delete:
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let action = UIContextualAction(style: .destructive, title: "") { (action, view, completionHandler) in
if let item = self.dataSource.itemIdentifier(for: indexPath) {
CoreDataManager.sharedManager.deleteItem(item)
}
completionHandler(true)
}
action.image = UIImage(named: "deleteSymbol")
let configuration = UISwipeActionsConfiguration(actions: [action])
return configuration
}
you declare below function as global function for the project and access any where you want
You can modify function by adding parameter which you want to use when button clicked. just simply add parameter in function and pass data when you call
func getSwipeAction(indexpath : IndexPath)-> UISwipeActionsConfiguration{
let action = UIContextualAction(style: .destructive, title: "") { (action,
view, completionHandler) in
completionHandler(true)
}
action.image = UIImage(named: "deleteSymbol")
let configuration = UISwipeActionsConfiguration(actions: [action])
return configuration
}
You can add these inner code in UITableView extension function and try to use it like that. You have to implement trailingSwipeActionsConfigurationForRowAt or leadingSwipeActionsConfigurationForRowAt in each of your view controllers.
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
return self.YourFunctionNameInExtension()
}
You can define a protocol like BaseTableViewProtocol and in this protocol's extension you can write your code. So when you extend your tableView class from this protocol you can use the function. For example,
protocol BaseTableViewProtocol { }
extension BaseTableViewProtocol {
func trailingSwipeActionsConfigurationForRowAt() {
//Something goes here
}
func leadingSwipeActionsConfigurationForRowAt() {
//Something goes here
}
}

SwipeCellKit action won't perform segue

I'm using SwipeCellKit for my TO DO List app. When the user swipes left it deletes the item, but when the user swipes right I want him to be able to set a reminder on this item, so I've created an actionset a reminder
this action should perform a segue which brings the user to a custom popup with a date picker in it. The problem is that when I click on the button to set a reminder the simulator quits with an uncaught exception. I've already tried to perform deletion from this button it works perfectly, I've also tried to perform another segue to another view controller from this button the simulator quits. Could someone tell me what I'm doing wrong here? Here's my code:
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? { if orientation == .left {
guard isSwipeRightEnabled else { return nil }
let setReminder = SwipeAction(style: .default, title: "Set a reminder") { action, indexPath in
self.updateModelByAddingAReminder(at: indexPath)
}
setReminder.image = UIImage(named: "reminder-icon")
return[setReminder]
}else{
let deleteAction = SwipeAction(style: .destructive, title: "Delete") { action, indexPath in
self.updateModel(at: indexPath)
}
// customize the action appearance
deleteAction.image = UIImage(named: "delete-icon")
// return [setReminder, deleteAction]
return [deleteAction]
}
Ok, I found problem in your options for cell.
From doc
The built-in .destructive, and .destructiveAfterFill expansion styles are configured to automatically perform row deletion when the action handler is invoked (automatic fulfillment).
And you need use destructive style for cell in editActionsForRowAt. Or use another options, for example
func tableView(_ tableView: UITableView, editActionsOptionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> SwipeTableOptions {
var options = SwipeTableOptions()
options.transitionStyle = .border
if orientation == .left{
//or none
options.expansionStyle = .selection
}else{
options.expansionStyle = .destructive
}
return options
}
Hope it's help.

TrailingSwipeAction: How to deactivate auto delete on swipe through?

I update my tablerowactions to the swift 4 equivalent to be able to set icons instead of text as the buttons that show when the user swipes left on a table-element. My problem is that the first defined action (in my case the delete action) automatically gets triggered if the users swipe through from right to left instead of just showing all available actions. I would like to deactivate this behaviour.
My code looks currently like this:
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .normal, title: "", handler: { (ac:UIContextualAction, view:UIView, success:(Bool) -> Void) in
// implemantion of delete-button here
// ...
success(true)
})
deleteAction.image = #imageLiteral(resourceName: "deleteIcon")
deleteAction.backgroundColor = .red
return UISwipeActionsConfiguration(actions: [deleteAction])
}
UISwipeActionsConfiguration has a property that allows you to turn this behavior off, called performsFirstActionWithFullSwipe. (Documentation)
So instead of:
return UISwipeActionsConfiguration(actions: [deleteAction])
do something like this:
let configuration = UISwipeActionsConfiguration(actions: [deleteAction])
configuration.performsFirstActionWithFullSwipe = false
return configuration

swift swipe tableview cell set image with trailingSwipeActionsConfigurationForRowAt AND editActionsForRowAt

swipe table view cell then show some option to delete and edit. I want to set full image. I have seen lots of demo code but then are with text and background image, I have need to create with whole image here is my code for ios 10 and ios 11 but I cant get success
with editActionsForRowAt Problem is image is repeate multiple time
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]?
{
let ArchiveAction = UITableViewRowAction(style: UITableViewRowActionStyle.default, title: " ") { (action , indexPath ) -> Void in
tableView.setEditing(false, animated: false)
}
let shareAction = UITableViewRowAction(style: UITableViewRowActionStyle.default, title: " ") { (action , indexPath) -> Void in
tableView.setEditing(false, animated: false)
}
ArchiveAction.backgroundColor = UIColor(patternImage: UIImage(named: "archiver.png")!)
shareAction.backgroundColor = UIColor(patternImage: UIImage(named: "bloquear.png")!)
return [ArchiveAction,shareAction]
}
with trailingSwipeActionsConfigurationForRowAt Problem is image not show properly. show white image
#available(iOS 11.0, *)
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let action = UIContextualAction(style: .normal, title: "sdfsdf", handler: { (action,view,completionHandler ) in
//do stuff
completionHandler(true)
})
// action.image = UIImage(named: "archiver.png")
action.backgroundColor = .black
let confrigation = UISwipeActionsConfiguration(actions: [action])
confrigation.performsFirstActionWithFullSwipe = true // default is false
return confrigation
}
Please give me any solution
I also experienced the problem of a white rectangle instead of the image when using .jpg. Using a .png worked for me.
Since you are using a .png already, did you try to load the image name without the .png extension? ("archiver" instead of "archiver.png")
Because the parameter description of init?(named name: String) says:
... For PNG images, you may omit the filename extension. For all other file formats, always include the filename extension.

Resources