What exactly does setEditing() do in Swift? - ios

I am implementing navigationItem.leftBarButtonItem = editButtonItem under viewDidLoad(), and it is said that I have to implement setEditing(_ editing: Bool, animated: Bool) as well. It seems like every editing functionality works great without setEditing function. What does it do??
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.leftBarButtonItem = editButtonItem
tableView.allowsMultipleSelectionDuringEditing = true
}
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: true)
tableView.setEditing(tableView.isEditing, animated: true)
}

and it is said that I have to implement setEditing(_ editing: Bool, animated: Bool) as well
Then "it is said" incorrectly.
The built-in editButtonItem of a UITableViewController automatically calls the table view's setEditing for you; there is no need, therefore, to duplicate that functionality. To be more precise:
The built-in editButtonItem of a UIViewController does two things:
It calls the UIViewController's setEditing(_:animated:) when tapped.
It tracks the UIViewController's isEditing property, and changes its own title accordingly (Edit or Done).
Moreover, UITableViewController's implementation of setEditing(_:animated:) calls setEditing(_:animated:) on its table view.
Thus, you would need to do that last step if this were not a UITableViewController. But it is, so you don't.

Related

UITableViewCell - distinguish full edit mode and delete row mode

I have UITableViewController and after user pres desired button, I have "full edit" mode. To enable this, I call this:
#objc func btnEditPressed(sender: UIButton) {
sender.isSelected = !self.isEditing
self.setEditing(!self.isEditing, animated: true)
self.tableView.beginUpdates()
self.tableView.endUpdates()
}
for each UITableViewCell, I have override func setEditing(_ editing: Bool, animated: Bool) method. In this, I hide some parts of cell in "full edit" mode.
Now, when the user swipe single table row, I show the delete button. However, in this "delete row mode" I dont want to hide any information from the cell. The problem is, that the same override func setEditing(_ editing: Bool, animated: Bool) for the cell is called as in the first case, leading to hiding cell content.
Is there an easy way, how to solve this, or I have to keep weak reference to UITableViewController and check the mode from the cell myself?
You have two options to handle this.
Check showingDeleteConfirmation in the cell's setEditing method.
Override willTransition(to:) and didTransition(to:) in your cell class.
If your table view is not in editing mode and the user performs a swipe-to-delete gesture, then your cell will experience the following sequence:
willTransition(to:) will be called with the mask including the value UITableViewCell.StateMask.showingDeleteConfirmation
setEditing will be called with editing set to true. The cell's showingDeleteConfirmation will be equal to true
didTransition(to:) will be called with the mask including the value UITableViewCell.StateMask.showingDeleteConfirmation
So the simplest solution is to update your cell's setEditing:
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
if showingDeleteConfirmation {
// User did a swipe-to-delete
} else {
// The table view is in full edit mode
}
}

UITableView, UISwipeActionsConfiguration, UIContextualAction - The pullView is not in a view hierarchy. This is a UIKit bug

I have a UITableView with swipe action that when swiping: A) displays a blank/empty area where the swipe action button should display; and B) the following line is logged to the debug console in Xcode:
[Assert] The pullView is not in a view hierarchy. This is a UIKit bug.
As of today, zero Google results exist for searching on the title of this question.
It turns out that the culprit was the following
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
if !editing {
rowSelectionState.removeAll()
loadData()
}
refreshView()
}
Specifically, the refreshView() call, which contains a tableView.reloadData() call, needs to be inside of the if !editing { ... } block. If not, when a swipe action is initiated, the swipe action appears to call setEditMode(true, ...), thus calling tableView.reloadData() which messes with the UISwipeActionsConfiguration's ability to be properly displayed.
Thus the above should look like this:
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
if !editing {
rowSelectionState.removeAll()
loadData()
refreshView()
}
}

Hide and show navigation bar for specific view without laggy animation

Is there a way to show navigation view on one view and not show it on another at the same time?
The problem: I have two view controllers - table and description view (called on cell click).
Table got a navigation bar, while description view - don't have it.
Table view:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.navigationController?.isNavigationBarHidden = false
}
Description view controller:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.isNavigationBarHidden = true
}
Everything works fine, but when i swipe for half screen back to table (keeping finger on screen, watching both views) - i don't see navigation bar (which works as expected with that code), and when i release finger - whole table view jumps, because nav bar is shown.
Is there a way to keep not seeing nav bar in description view and see it all the time in table view?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.navigationController?.setNavigationBarHidden(false, animated: true)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(true, animated: true)
}
You can hide the navigation bar while doing the segue (earlier). If you're doing it programmatically:
yourVCToBePushed.navigationController?.isNavigationBarHidden = true
If you're doing it in the storyboard, do similarly inside prepareForSegue:
let yourVCToBePushed = segue.destination as! YourVCToBePushed (type)
yourVCToBePushed.navigationController?.isNavigationBarHidden = true
You can also create your own "navigationView" inside tableView header, and add buttons there.

Back button animation with large title jumps

We have two UIViewController with an UINavigationController.
In the first presented VC inside of viewWillAppear(_ animated: Bool) we do:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if #available(iOS 11.0, *) {
navigationController?.navigationBar.prefersLargeTitles = true
navigationController?.navigationItem.largeTitleDisplayMode = .always
}
....
Inside of the second VC we deactive that behaviour with inside of viewWillAppear(_ animated: Bool):
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if #available(iOS 11.0, *) {
navigationController?.navigationBar.prefersLargeTitles = false
}
...
The transition animation to the second VC is smooth while tapping automatic generated back button causes the navigation controller title to create a strange jump to large title instead of the normal grow to large title animation as it does for example in the Messages App.
If i tap the tabbar icon as "back" operation, it does the right transition animation.
Any idea what could cause that issue or how i can fix it?
on the second view controller set the largeTitleDisplayMode to .never
you won't need to set the prefersLargeTitles to false.
To clarify things here, you've to set the largeTitleDisplayMode directly for the navigationItem of the view controller, not the navigation controller!
self.navigationItem.largeTitleDisplayMode = .never // This fixes the issue
self.navigationController?.navigationItem.largeTitleDisplayMode = .never // This doesn't work / Title will stay large
#dave's answer worked for me! Thanks! Here's the code that I used in its entirety:
FirstViewController:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if #available(iOS 11.0, *) {
navigationItem.largeTitleDisplayMode = .always
navigationController?.navigationBar.prefersLargeTitles = true
}
}
}
SecondViewController:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if #available(iOS 11.0, *) {
navigationItem.largeTitleDisplayMode = .never
}
}
}
One should make force layout of navigation bar right after switching off large title
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.navigationBar.prefersLargeTitles = false
navigationController?.navigationBar.layoutIfNeeded()
}
This cancels out large navigation title immediately.
I had the same transition bug: from large title to small one or backwards. It was not growing/diminishing from one state to the another, but it was staying ugly on the screen for 1 sec, then just jumping from large to small or vice-versa.
The simple solution:
Make sure each view controller has a navigationItem in the Storyboard.
And for each navigationItem, set the corresponding Large Title
value:
Also, you don't need to set anything in viewDidLoad,viewWillAppear, etc. related to largeTitle. Just what I showed above.
For me it was something completely different. In my project we set a custom back button without title on every VC. Standard way to do this for ages was to set an empty BarButtonItem like this:
navigationItem.backBarButtonItem = UIBarButtonItem()
Removing that line fixed the jumping back button when moving from a VC with large title to one without large title. Still having design requirement I found out that since iOS 14 this can be done much more neatly:
navigationItem.backButtonDisplayMode = .minimal
So just replace setting a new BarButtonItem with setting the display mode.

Stay in edit mode when push to another view and go back IOS Swift

In some cases I want my tableview in editmode when the view appears. To do this I set editMode = true when segue to the view and call setEditing in viewWillAppear method. When the view is in editing mode and the view is pushed to another view and popped back to this view, the table is not editable anymore. Could someone tell me how to stay in editing mode or do this a better way?
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
self.setEditing(self.editMode, animated: false)
}
It was a simple fix..
override func setEditing(editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
if (editing){
print("Editing")
self.editMode = true
}
else {
print("Done editing")
self.editMode = false
}
}
TableViews have their own properties to track whether it is in edit mode or not:
yourTable.isEditing
So no need to add "editMode" to the table and manually set it whenever the table goes in or out of editing mode.

Resources