I'm new to iOS development and now I'm trying to achieve this effect:
As you can see, when dragging, the cell's background is transparent and items above or below it will move away with a fluent animation.
Either UIKit or SwiftUI will do. Any advice will be appreciated. :)
This is achievable with the help of table delegates.Just implement below methods.
func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
return .none
}
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let itemToReorder = yourList[sourceIndexPath.row]
yourList.remove(at: sourceIndexPath.row)
yourList.insert(itemToReorder, at: destinationIndexPath.row)
}
Don't forget to enable disable editing mode when needed or not.
Use this for enabling and disabling
tableView.setEditing(true, animated: true)
I know that drag and drop can be used to transfer data throughout and between apps. I'm not interested in that. All I want is to use drag and drop functionality to reorder a table view without transferring data. How do I do that?
If you are performing a drag on a single item locally, you can use tableView(_:moveRowAt:to:). In order to do this, you need to implement UITableViewDragDelegate.
Setup
Start by setting your delegates. Setting dragInteractionEnabled is required for iPhones.
func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
tableView.dragDelegate = self
tableView.dragInteractionEnabled = true
}
UITableViewDragDelegate
Notice that the array is returning a single item. If you return more than one item, then the UITableViewDropDelegate methods will be used instead of tableView(_:moveRowAt:to:). You must set a local object.
func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
let dragItem = UIDragItem(itemProvider: NSItemProvider())
dragItem.localObject = data[indexPath.row]
return [ dragItem ]
}
Moving
This is where the moving happens and is actually a part of UITableViewDelegate.
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
// Update the model
let mover = data.remove(at: sourceIndexPath.row)
data.insert(mover, at: destinationIndexPath.row)
}
You can also use tableView(_:canMoveRow:at:) and tableView(_:targetIndexPathForMoveFromRowAt: toProposedIndexPath:) if needed.
You can read more here...
Drag and Drop
Adopting Drag and Drop in a Table View
I found the answer for this really confusing, and found that implementing UITableViewDragDelegate is not what is required here. Basically:
If you want to just be able to reorder a table view using drag and drop then don't implement UITableViewDragDelegate and instead just implement the func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) method of UITableViewDelegate as such:
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
// Update the model
let mover = data.remove(at: sourceIndexPath.row)
data.insert(mover, at: destinationIndexPath.row)
}
Otherwise if you want to be able to drag and drop items between apps or views etc then you will need to implement the UITableViewDragDelegate and the methods that come with that, e.g,
func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem]{
// Handle here
}
and
func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) {
// Handle Drop Functionality
}
etc. Not going into too much detail on that, but you will find tutorials on dragging / dropping between different views and apps elsewhere. However, for a simple reordering of tableview cells using drag and drop, go with the first option and don't implement UITableViewDragDelegate.
Hope this is helpful, as I got a bit confused between the two approaches.
You can adopt Drag and Drop in a Table View
In order to do that, in addition to regular delegate = self and dataSource = self, you need to add
tableView.dragInteractionEnabled = true // Enable intra-app drags
tableView.dragDelegate = self
tableView.dropDelegate = self
That's it. The rest of it is to figure out how to implement the delegate methods above for both visual effects and data transfer.
When data is transferred between apps, the receiving app implements the methods of dropDelegate, the sending app implements the methods of dragDelegate. Now it's the same table view, it implements both.
You can stop reading now, instead, you can read the link at the top. Below is my understanding of the linked document. I'm half-baked in this topic, but I have made it work myself.
For dragDelegate
Implement func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] for providing data. In many/most cases, [UIDragItem] array only contains 1 element, cuz usually only one item is dragged. UIDragItem contains an NSItemProvider object which carries transferred data.
For dropDelegate
Implement func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool to tell the table view whether this drop action should be process.
Implement func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal to add a visual indication of dropping item. .move will add a "moving there" animation, .copy will add a plus sign to indicate copy action. If this func is not implemented, you get no visual cue, and I don't recommend it, but it's still optional.
Implement func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) for actual dropping the data. Regular moving rows is like moving a row from A to B. A is the original and B is the destination. Now in the case of drag and drop, one way you can do is that, you can get destination B from coordinator.destinationIndexPath. A's indexPath can be stored beforehand in the dragDelegate method UIDragItem's itemProvider, and now get it back thru coordinator.session.loadObject(ofClass:). Now you have A and B, you can either do tableView.move(at:to:) or delete one row then insert the row to the new indexPath.
Again, you can read Apple's document in the link at the top.
You dont need UITableViewDragDelegate for this.
First add this when ever you want to start editiyng/rearranging you table:
table.isEditing = true
Then add these delegate methods:
func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
allRides.swapAt(sourceIndexPath.row, destinationIndexPath.row)
}
After Swift 4 try this:
override func viewDidLoad() {
super.viewDidLoad()
self.deviceOrderingTableView.delegate = self
self.deviceOrderingTableView.dataSource = self
}
extension DevicesOrderingVC: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
self.model.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//configure Cells here
}
func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
true
}
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
// Update the model
self.model.swapAt(sourceIndexPath.row, destinationIndexPath.row)
debugPrint(self.model)
self.deviceOrderingTableView.reloadData()
}
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
return UITableViewCellEditingStyle.none
}
func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
return false
}
}
Output image:
Before I've just used tableView.setEditing(true, animated: true) in order to make rows in tableView move. I used the code:
override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
return true
}
override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
array.insert(array.remove(at: sourceIndexPath.row), at: destinationIndexPath.row)
}
I've found that there is an interesting protocols: UITableViewDragDelegate and UITableViewDropDelegate. I have to implement 2 funcs and add tableView.dragDelegate = self; tableView.dropDelegate = self; tableView.dragInteractionEnabled = true
func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
return []
}
func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) {
}
What is surprising to me is that this code is enough to make rows move. And I have a simple question - is it that simple? May be I miss something, may be I have to add something?
I have a UITableView which I need to both reorder and allow for cells to be deleted by swiping left on a cell to reveal the Delete action. Is it possible to have both at the same time?
For the table I have:
tableView.allowsSelectionDuringEditing = true
tableView.isEditing = true
For the datasource:
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
// I do nothing here
}
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
// This is where I rearrange my data source array
}
And for the delegate:
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
return .none
}
func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
return false
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let delete = UITableViewRowAction(style: .destructive, title: "Remove") { action, index in
// delete row
}
delete.backgroundColor = UIColor.appRed
return [delete]
}
So what I want to have is:
Reorder control allowing me to move a cell
Swipe left to reveal cell actions
And I need these to be visible and work at the same time. Can this be done?
Swipe to delete is not possible when your UITableView is in editing mode.
If you want to hide the delete button on the left when in editing mode you have to implement the following methods:
override func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
return false
}
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
return tableView.isEditing ? .none : .delete
}
I think this is the closest you can get.
I am new to swift and havne't programmed in objective C. So i'm new :-)
trying to do something fairly simple. Link a table view cell click to call a method in the controller like so
http://prntscr.com/4m9kf7
Edit:
i have these methods in my MasterController
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
override func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
I have added breakpoints in all 4 of them.
and when clicking on the cell it doesn't stop in either of them.
See Screen shot:
http://prntscr.com/4m9ql0
You need to implement the didSelectRowAtIndexPath delegate method and put your handling code inside it.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//CODE TO BE RUN ON CELL TOUCH
}
For Swift 4:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("row selected: \(indexPath.row)")
}